I didn't think it would take almost three days to make this alone, but it's all from scratch. JUST make the reactions feature, yes that's what follows below ๐
Don't do the analysis first, just make the database, trial & error and refactoring. Some of them are just lazy, but the one who clearly wants to code immediately ๐
Once in this article, I'll explain what the features are, how to make them, but it's not a step-by-step tutorial.
Who knows, some of you want to make it too, right, so you can cheat a little bit, but just take the 'good'.
Gasss right away...
Features
First of all, I thought about the rough picture I wanted to make like this reaction.
And here it is, roughly the features are like this:
-
Multiple Reactions
There are claps, wow etc. and as easy as possible if for example in the future you want to add another reaction.
-
Batch Reactions
Can give a similar reaction several times, for example 20x claps, 10x wow etc.
-
Section Reactions
You can know where the user gives a reaction, for example giving 10x claps in the section titled 'Features' like this section.
Concept
So actually it took a long time to create a REST API, a lot of refactoring, a long time to create the database schema and other things that are towards BE.
There must be a lot of things that are not accurate, please be informed, it's not BE's child that's the problem โ
Database
To be honest, after three years like that, I just dealt with the database again. Last time I took care of the database when making the application for the final exam of the lecture.
First, decide where you want to host. The SQL noSQL problem is not a problem, because it is still a trial and error, so try to find a free one and get MongoDB Atlas shared hosting, it's pretty good.
Because I also use prisma for the ORM, so it's not too complicated to migrate to another database.
Let's go to the database schema. This is only focused on the reactions feature, there is actually a field for views and shares but I deliberately did not include it.
First, I created the ContentMeta table. This is to save the post data.
It is necessary to save the title, date, content, etc. because the post data is built locally from mdx, so just save the slug.
model ContentMeta {
id String
slug String
createdAt DateTime
}
Then this is the important thing, the Reaction table, to save the reactions.
Reaction model {
String id
type ReactionType
String section
count Int
sessionId String
createdAt DateTime
contentId String
}
enum ReactionType {
CLAPPING
THINKING
AMAZED
}
Let's break it down slowly:
-
type
It is very important, because it uses multiple reaction, this
type' must be used. I use the predefined enum
ReactionTypefor the data type, so the contents can only be between
CLAPPING,
THINKING, and
AMAZED`.If you want to add another reaction, you can add a new item to the enum. Another option is to change the data type to String, so for example if you want to use the reaction all emojis can also be used.
Why not create a relation to the new table?
It's possible, but I think it's going to be complicated later on in the query, using an enum is also enough, everyone should be careful with type safety too โ
-
section
Just save the title of the section that is active when the user clicks on the reaction. For example, when the user reads the title 'Database', then this section will contain 'database'.
Just run the IntersectionObserver, observe all existing section titles, the last one 'isIntersecting' is used as the value of this section .
-
count
This makes the total batch reaction. So when you click the reaction, it doesn't actually send directly to the API, but it is delayed for a few milliseconds, debounce that is.
It's as simple as this: after clicking the reaction several times in a short period of time, the number of clicks is first collected (for example 10 clicks), the results are immediately sent to the API, and the number of clicks is stored in this
count
.For me, this method makes more sense, than requesting 10x API and making 10 row reactions with the same data. Let's save the database storage.
-
sessionId
Save 'who' is reacting. Not the user's name or identity, but I use the user's ip address.
Eits, don't save the ip address either, because this includes very sensitive data for the user.
I hashed the ip address, plus 'digaremin' as well so it can be fixed hehe. So no one knows the user's ip address, including me who created it.
The code looks like this:
export const getSessionId = (req: NextApiRequest) => { const ipAddress = req.headers['x-forwarded-for'] || 'localhost'; // hashes the user's IP address and combines it with // a salt to create a unique session ID that preserves // the user's privacy by obscuring their IP address. const currentSessionId = createHash('md5') .update(ipAddress + process.env.SALT_IP_ADDRESS, 'utf-8') .digest('hex'); return currentSessionId; };
Or if you want to inspect directly, gasss here.
Then why should there be a sessionId?
This is used to 'limit' or limit the number of reactions. I groupBy type
where sessionId
, then the count
is calculated.
If the result has reached the max limit, it means that the user can no longer give a reaction.
REST API
This only needs one endpoint, I made it /reactions
so it can only accept the POST method. Accepts the data slug
, type
, section
, and count
.
As explained before, all data sent is handled in the Front-End. Except for the sessionId
one. ``sessionId'' is generated after the API request.
Well, there is one more endpoint to add, GET /content
. This shows the details of the number of slug
reactions at the moment, to be used in the component later.
From the previous two tables, you can get this data (response /content
):
{
"meta": {
"reactionsDetail": {
"CLAPPING": 65,
"THINKING": 45,
"Amazed": 48
}
},
"metaUser": {
"reactionsDetail": {
"CLAPPING": 15,
"THINKING": 15,
"Amazed": 15
}
},
"metaSection": {
"features": {
"reactionsDetail": {
"CLAPPING": 0,
"THINKING": 4,
"Amazed": 0
}
},
"database": {
"reactionsDetail": {
"CLAPPING": 0,
"THINKING": 0,
"Amazed": 6
}
},
}
}
meta
is the total number of reactions from all users, metaUser
is the total number of reactions of the current user, and metaSection
is the number of reactions on a specific section.
Maybe some people say, it's the `metaSection', why not just make it an array?
To be honest.... it sucks too ๐
But because in the FE logic component I only take data per section (not looping), I prefer to take the data using the `metaSection[section]' method instead of the array which must be filtered first ๐
Components
This is the part that I enjoy the most.
First, to retrieve data from the API, I use SWR. Quoted from SWR's own site:
With SWR, components will get a stream of data updates constantly and automatically. And the UI will always be fast and reactive.
It's really good to use this SWR, especially the data mutation.
Now make the reactions component
Component details:
- Show reactions that express
claps
,wow
, andhmm
. - Display emoji animation on hover.
- Batch animation appears after clicking or clicking several times.
- Add animation to the reactions counter.
- Change the emoji once you hit the limit.
For the emoji animation (point 2) I took it here yes.
For animation matters, still faithfully use framer motion ๐
When the user clicks on the reaction, the animation appears, when the onClick' event is just a push, the
xand
y` animation values are random. The animation value was rendered so the component is framer motion.
With the random values of x
and y
, so when you click a few times, the animation will not be monotonous in that direction.
Are you confused? hehe sorry, try checking full code.
Final Result
This is the final result, try to give reactions, until the limit, there is an animation ๐
Although it still needs a lot of adjustments from styles, pictures, layout or performance, I am happy with the final result at the moment.
Summary
Although making this feature is quite challenging especially in BE, but I quite enjoy all the process. Although it took almost three days cuy ๐
Recap of tech stacks / libraries used:
- MongoDB Atlas for database hosting.
- Prisma for its ORM.
- SWR to fetch data from API.
- Framer Motion for animation.
- Animated Fluent Emoji
WAITTT, is that section reactions? The API already provides a
metaSection
, what is it used for?
If the data is already there, it is safe, even if the implementation is not yet. But I already have a plan and an idea of โโhow I want to use it. Just wait for it ๐
But it will be updated on Twitter, not a special post will be made.
THANK YOU I have read to the end. See you next week in the next post ๐