Post

Optimization) useRecoilState vs useState

Optimization) useRecoilState vs useState

This post was migrated from Tistory. You can find the original here.

Problem

Every time a context menu is opened or closed by right-clicking on a single Message, all Messages re-render.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//Chat.tsx
    const [menuState, setMenuState] = useRecoilState<contextMenuInterface>(contextMenuState);
    ...
    return (
        <>
            {user.sub !== "0" &&
                <S.BodyContent>
                    <S.Container>
                        <S.Chat>
                            <ChatInfo />
                            <Messages />
                            {menuState.show &&
                                <SMenu.CloseWrapper onClick={closeWrapperHandler} >
                                    <ContextMenu x={menuState.x} y={menuState.y} />
                                </SMenu.CloseWrapper>
                            }
                        </S.Chat>
                    </S.Container>
                </S.BodyContent>
            }
        </>
    )

The Chat component is structured as above, and the Messages component contains Message components.

1
2
3
4
5
6
7
8
9
10
11
12
13
//Message.tsx
    const [menuState, setMenuState] = useRecoilState<contextMenuInterface>(contextMenuState);
    ...
    const msgPContextHandler = (e: React.MouseEvent<HTMLParagraphElement>) => {
        e.preventDefault();
        const { clientX, clientY } = e;
        setMenuState({
            show: true,
            x: clientX.toString(),
            y: clientY.toString(),
            msg: { id: e.currentTarget.id },
        })
    }

This is the handler that runs when you right-click on the message’s p tag inside the Message component.

Since the ContextMenu component lives in Chat, I used setMenuState from Recoil, which is global state management.

The problem here is that menuState is declared in every Message component, so calling setMenuState from a single Message component causes every Message component to re-render.

Solution

I had placed the ContextMenu component in Chat thinking it made sense to have only one instance of it, but on reconsideration, it isn’t always rendered to the DOM, and it was inevitably forcing every Message component to re-render.

1
2
3
4
5
6
//Messag.tsx
        {menuState.show &&
            <SMenu.CloseWrapper onClick={closeWrapperHandler} >
                <ContextMenu x={menuState.x} y={menuState.y} />
            </SMenu.CloseWrapper>
        }

I moved the ContextMenu component from Chat into Message, and instead of Recoil, used useState to create state per Message.

You can see that only the Message that triggered the ContextMenu re-renders.

Remaining question

This does prevent every message from re-rendering when one message triggers the action, but it does mean the virtual DOM is now watching more state. I’m still not sure whether it’s fine for the amount of state to grow like this, as long as it doesn’t change frequently.

This post is licensed under CC BY-NC 4.0 by the author.