Conceptually speaking, users should have an account when you want to retain information about them. However, not every web app is suited for this implementation. Personally, I find the task of signing up for websites arduous, especially when I only want to complete one task and never visit the website again. Unique identifiers are one way of solving this problem.
The Anonymous User Concept
My friend is a big fan of the question and answer website Quora. After graduating from bootcamp, I suggested that I could build a personal website for him. After going back and forth on the design, we settled on a personal website where people could submit questions to him and he (and only he) could answer them. Then, depending on how much he liked their question, the user would get some kind of reward. The only catch was he wanted the users to stay anonymous.
I researched cookies and IP addresses as a means of tracking users, until I hit on a simple idea. Why not use cuid?
ID Generation Libraries
Created by Eric Elliot, cuid is a library that creates collision-resistant alphanumeric strings like this: ch72gsb320000udocl363eofy. There’s a very, very, infinitesimal chance that a repeat id could be made.
Cuid is not the only library that can achieve this. Uuid is a similar library that returns a string that might look like this: 9c5cc850-d2b6-11ea-87d0-0242ac130003. This project could have used either.
Ultimately, I chose cuid because it did not take up as much space aesthetically speaking.
Adding a Question Code
The backend was built out on Ruby on Rails. For the Question table, I added a code attribute to handle storing the unique id.
create_table "questions", force: :cascade do |t|
t.string "title"
t.string "context"
t.string "code"
t.integer "points"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
I could have, and should have, also tied the id generation as well. It is generally a good idea to contain id generation on the server side. Looking back, I should have done it this way, as creating the cuid in the frontend presented its own challenges.
Frontend Problems
The front end was created with React and Redux.
When I first built out the form to submit questions to, I initially tied the id generation to its own function. This did not work. As this was my first time working with Redux, I misunderstood the basic principles of how the technology worked.
To test out the best method of creating the id in the frontend, I tried creating the id in a variety of blocks. Each time I checked the backend and saw that a code was not created when a question was.
However, when I printed the code to the console, a code was printed. I decided that this was an async issue: the code was created after being sent to the backend. In other words, the question was created too quickly for the code library I used to create.
const [code, setCode] = useState('')
I used hooks to set the code creation in state.
const handleSubmit = e => {
e.preventDefault()
setCode(cuid())
if(title.length !== 0 || title!==" "){
handleShow()
} else {
window.alert("Please write something in the question field.")
}
}
If you are familiar with React but not hooks, think of the setCode function as setState but just for that individual code.
As you can see, I decided to tie the code creation to the handleSubmit function. This allowed the code to be created at the time the submit button was clicked. After tying it to the submit button, the code, along with the question, was saved to the backend. Redux allowed for quick state changes which ultimately allowed me to do other things with the generated code.
Email Functionality
Now, any reasonable user probably does not want to take the time to write down a code. They could just copy and paste it to a file on their computer, but by far the easiest solution is to just click a button and store it away in some kind of filing system.
Now what kind of filing system would a user most likely have?
Email!
After looking around, I discovered Email.js, a free email automation tool. Email.js allowed me to create a template that would be sent to a user. It also let me code in variables to the template which would be important for the user’s code.
When a user submits a question, a modal appears with the option to send the code to their email.
An email is then sent to the user's inbox with the code and links back to the site and rewards page.
Setting up the email functionality involved sending the newly generated code down to the email component as a prop.
const EmailForm = ({code}) => {
...
}
The email form receives the code from the parent component.
const handleSubmit = (event) => {
event.preventDefault()
if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(email)) {
setError(true)
} else {
const templateId = "personal_site";
const template_params = {
"email": email,
"code": code
}
const service_id = "SECRET";
const API_KEY = "SECRET";
sendCode(service_id, templateId, template_params, API_KEY)
}
}
Setting up the email functionality involved four parameters: a service id and API key provided by Email.js (which I have labeled "SECRET), the id from the template you intend to use, and the variables the template contains. In this case, the template's variables are the code and the user's email. I also used standard regex to check for email error handling.
Redeeming their Prize
Of course, sending out a unique code each time presents another problem: what if a user sends out multiple questions? Shouldn’t they be able to combine the codes and gather as many points as the deserve?
After some brainstorming, I wrote an algorithm to handle multiple inputs.
const [code, setCode] = useState("");
const [point, setPoint] = useState(0);
const [usedCodes, setUsedCodes] = useState([])
An array of used codes are held in state. That means a user can’t input the same code and expect to get double the points for it.
const validateCode = (code) => {
const found = questions.find(q => q.code === code)
if(found){
setUsedCodes(usedCode.concat(found.code))
const currentCode = usedCodes.find(c => c === code)
if(!currentCode){
setPoint(found.points + point)
} else {
window.alert("You have already entered that code.")
}
} else {
window.alert("That code does not exist.")
}
}
In the validate code function, the algorithm checks for unique codes. If a unique code is found, that code is added to the used codes array. Each question, and therefore code, has a set number of points attached to it. As the user submits codes, their points are added up. With these points, the user has access to more and more rewards. In this case, the rewards were my friend's wine recommendations.
Lessons Learned
There were many mistakes I made with this project. Looking back, I would have probably generated the id on the server side to save myself a lot of troubleshooting.
However, it was also a great experience to learn new technologies such as Redux and Email.js.
In the end, the app accomplishes the task of allowing a user to revisit the site and retain information without them having to register with the site.
Top comments (3)
Good points! Thanks for sharing. I've faced the same issue. I've built a react app focusing on survey and map. Users share surveys which are integrated with map to the public users and the anonymous users fill the survey and add markers on maps. The point is I've to create an identifier for each anonymous user so I know who this data come from. I don't know which way is better, to use IP address, which I've trouble with it or using ID generation libraries. I need your thoughts here as you have experiences with such things. Thanks in advance!
Nice experience
github.com/kailanak1/David-ask-me-...
Is this the one you mentioned?
Yes! Thank you for your comment.