reactjsnext.jsrenderingmobxswr

Partial Component Rendering on State Update in MobX or SWR Mutation


Problem: When updating the state in MobX or calling a mutation in SWR, all components are being re-rendered, although I want to update only one element.

Example code: In the React sandbox, rendering occurs twice when selecting an element (likely due to StrictMode): [Link to React sandbox]

In the Next.js sandbox, rendering occurs five times when selecting an element: [Link to Next.js sandbox]

I've tried calling mutate when selecting a post and adding the active property to determine whether the element is active or not. However, this approach didn't help. Moreover, it doesn't fully solve my problem because if I navigate to another section where the same post exists, it won't be highlighted.

What I want to achieve: When selecting an element (for example, a post), it is stored globally and highlighted. If the user navigates to other sections (without selecting a new post), upon returning to the section with the selected post, it continues to be highlighted. This functionality resembles what's found in music players, where selecting a track triggers an animation, and when returning to the section, the animation persists, allowing the user to pause it.

I also have an idea to add a unique identifier to each element and then search for it in the DOM tree to change it specifically. However, I'm not sure if this is the right approach and whether it's even worth trying to implement.

I would be grateful for any help or recommendations to solve this problem!

I am using: Next.js 14.2.3 React 18 SWR 2.2.5 MobX 6.12.3 mobx-react-lite 4.0.7


Solution

  • Right now you subscribe to the active post in every Post component. So when the active one changes, all of them also rerender, because they are subscribed to this change. If you want to reduce the number of rerenders you need to pass isActive as a prop instead, and subscribe to the active post only in the parent List component.

    You also need to keep stable references to all the props you pass to the Post, to it can be memoized by observer, to do this I passed selectPost={statePosts.setCurentPost} directly to the Post.

      return (
        <div>
          {posts.map((post, index) => {
            const isActive = currentPost && currentPost.id === post.id;
    
            return (
              <RenderPost
                post={post}
                index={index}
                selectPost={statePosts.setCurentPost}
                isActive={isActive}
                key={post.id}
              />
            );
          })}
        </div>
      );
    

    So the number of rerenders are now only 2, 1 for component that was active and 1 for component that becomes active.

    I've disabled strict mode to make it a bit clearer:

    https://codesandbox.io/p/devbox/twilight-moon-forked-6w3nx6