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.

