Requirement
Suppose you have a list of items ( it could be a product list, user ranking, or whatever). You asked to implement the upvote with React. How to make it appealing?
In the case of this article, I would demo a list of products, and there will be a button to upvote a product item.
First few lines of code for a simple product list
Assume we are going to fetch a list of products from the server and keep it in state. When the user clicks on the upvote button, we will increase the vote by one.
Initiative
- What is going to change when the list reorder? The position of product item
getBoundingClientRect](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) WebAPI provide us DOMRect of an element (which are
left
,top
,right
,bottom
,x
,y
,width
, andheight
properties)
Great. We have a product item's top
and left
. These properties are likely to change when the element goes up or down in the list.
- How to keep the previous position of items so we can add animation when there is a change? If we can have a reference of the product list, we can trigger some action whenever there is a change. Also, we can compare the difference between the previous position and the next position
- We need to have a way to intervene in between state changes to add animation. What React hooks should we use here?
useLayoutEffect to read layout from the DOM and synchronously re-render. Updates scheduled inside useLayoutEffect will be flushed synchronously before the browser has a chance to paint.
Okay, now we know how we can access the state of layout in between
- The tricky thing here is how to make the upvoting smooth. CSS Transitions came to my mind such a solution.
transform and transition go with translate
Implementation
- Add ref to the product list with createRef
- Create a custom hook to separate the logic
- An object to store DOMRect of every single item and a boolean ref to not running animation on the first run
To keep track of the DOMRect, we use product id. The origin key must be a not-changed unique key so that the product id would be the best in this case.
- useLayoutEffect - the most important part
The logic here is to check every item on the list.
const previous = origins.current[key];
is the previous position of the item
const next = child.getBoundingClientRect();
is the next position of the item after list reorder
This line of code is for checking the differences. If there is a difference, we applied animation to this item.
Using transform and transition in animation
Issue
I found an issue when scrolling a list. It causes the product element position to change. I added the code to update the item position when a scroll event trigger.
Source code
You can find all source code here: ( with React 18, Typescript ) https://github.com/toantd90/react-flip.
Any comments would be appreciated!!!
Top comments (1)
Decent solution. Since updates to refs don't cause rerenders, placing it as a dependency for useLayoutEffect is pointless as this will never trigger the effect to be called again.