DEV Community

loading...

Optimizing a long list for re-renders using useReducer ,memo and useCallback.

abrehamgezahegn profile image Abreham ・2 min read

Long lists in react that can't be paginated can cause expensive re-renders reacting to a small change, rendering the whole application obsolete, get it :)?

In this post, we will explore that problem using a simple todo app with a very long list of todos for demonstration purposes. The knowledge can be applied to any similar situation not limited to todo apps.
 

Alt Text

 
 
 

The problem

 
Let’s assume a to-do list with 1000 items. And each item has a completed status and toggle.
N - not complete and
C - completed

Now let’s toggle item 3's completed status,

Alt Text
The whole todo item component is re-rendered even though only item 3 is changed. This will cause a major lag. The effect is noticeable even for simple text-based list.

 
 

The solution

 
Let's introduce reducer to handle the state,

const [state, dispatch] = useReducer(todoReducer, initialState);
Enter fullscreen mode Exit fullscreen mode

dispatch doesn’t change between state changes. So we can leverage that to avoid re-renders. We basically declare our context state as above.

Our toggle function in context. Here we will have to wrap the function with useCallback and provide only dispatch as a dependency. By doing this we make sure the function isn’t re-created every time the state is changed. And it will help when passing the function as a prop.

 const toggleCompleted = useCallback(
   (id: number) => {
     dispatch({ type: "MARK_AS_COMPLETED", payload: id });
   }, 
   [dispatch]
 );
Enter fullscreen mode Exit fullscreen mode

The only catch here is that we cannot access the latest state in this function. Because it’s never updated with the state.

To overcome that we will have to access our state in the reducer.

else if (action.type === "MARK_AS_COMPLETED") {
   // here we can get the latest state
   const updated = state.todoList.map((item) => {
     if (item.id === action.payload) {
       return { ...item, completed: !item.completed };
     }
     return item;
   });
   return {
     ...state, 
     todoList: updated,
   };
 }
Enter fullscreen mode Exit fullscreen mode

And the todo item will be wrapped with memo. By doing this we make sure that todo and toggleCompleted stay the same between re-renders. And memo will be able to avoid the re-rendering of the component.
And when using memo we cannot access a context inside that component. We will have to pass the values as a prop.

export default memo(TodoItem);

Enter fullscreen mode Exit fullscreen mode

That’s it. TodoItem is memorized.
 
 
Now let's try toggling item 3.
 

The result

 
Alt Text

Only item 3 is re-rendered.
 
 
.
.
.
mic drop

Discussion (1)

pic
Editor guide
Collapse
rima13panchal profile image
rima13panchal

Very nice.. It is nice if you share github repo