Reputation: 1257
I'm building a Chat app, I'm using ContextAPI to hold the state that I'll be needing to access from different unrelated components.
A lot of rerendering is happening because of the context, everytime I type a letter in the input all the components rerender, same when I toggle the RightBar
which its state also resides in the context because I need to toggle it from a button in Navbar
.
I tried to use memo
on every components, still all the components rerender everytime I interact with state in context from any component.
I added my whole code simplified to this sandbox link : https://codesandbox.io/s/interesting-sky-fzmc6
And this is a deployed Netlify link : https://csb-fzmc6.netlify.app/
I tried to separate my code into some custom hooks like useChatSerice
, useUsersService
to simplify the code and make the actual components clean, I'll also appreciate any insight about how to better structure those hooks and where to put CRUD functions while avoiding the excessive rerendering.
I found some "solutions" indicating that using multiple contexts should help, but I can't figure out how to do this in my specific case, been stuck with this problem for a week.
EDIT :
RightBar
toggle button which also causes a full rerender.Upvotes: 0
Views: 1913
Reputation: 202874
Splitting the navbar and chat state into two separate React contexts is actually the recommended method from React. By nesting all the state into a new object reference anytime any single state updated it necessarily triggers a rerender of all consumers.
<ChatContext.Provider
value={{ // <-- new object reference each render
rightBarValue: [rightBarIsOpen, setRightBarIsOpen],
chatState: {
editValue,
setEditValue,
editingId,
setEditingId,
inputValue,
setInputValue,
},
}}
>
{children}
</ChatContext.Provider>
I suggest carving rightBarValue
and state setter into its own context.
NavBar context
const NavBarContext = createContext([false, () => {}]);
const NavBarProvider = ({ children }) => {
const [rightBarIsOpen, setRightBarIsOpen] = useState(true);
return (
<NavBarContext.Provider value={[rightBarIsOpen, setRightBarIsOpen]}>
{children}
</NavBarContext.Provider>
);
};
const useNavBar = () => useContext(NavBarContext);
Chat context
const ChatContext = createContext({
editValue: "",
setEditValue: () => {},
editingId: null,
setEditingId: () => {},
inputValue: "",
setInputValue: () => {}
});
const ChatProvider = ({ children }) => {
const [inputValue, setInputValue] = useState("");
const [editValue, setEditValue] = useState("");
const [editingId, setEditingId] = useState(null);
const chatState = useMemo(
() => ({
editValue,
setEditValue,
editingId,
setEditingId,
inputValue,
setInputValue
}),
[editValue, inputValue, editingId]
);
return (
<ChatContext.Provider value={chatState}>{children}</ChatContext.Provider>
);
};
const useChat = () => {
return useContext(ChatContext);
};
MainContainer
const MainContainer = () => {
return (
<ChatProvider>
<NavBarProvider>
<Container>
<NavBar />
<ChatSection />
</Container>
</NavBarProvider>
</ChatProvider>
);
};
NavBar - use the useNavBar
hook
const NavBar = () => {
const [rightBarIsOpen, setRightBarIsOpen] = useNavBar();
useEffect(() => {
console.log("NavBar rendered"); // <-- log when rendered
});
return (
<NavBarContainer>
<span>MY NAVBAR</span>
<button onClick={() => setRightBarIsOpen(!rightBarIsOpen)}>
TOGGLE RIGHT-BAR
</button>
</NavBarContainer>
);
};
Chat
const Chat = ({ chatLines }) => {
const { addMessage, updateMessage, deleteMessage } = useChatService();
const {
editValue,
setEditValue,
editingId,
setEditingId,
inputValue,
setInputValue
} = useChat();
useEffect(() => {
console.log("Chat rendered"); // <-- log when rendered
});
return (
...
);
};
When running the app notice now that "NavBar rendered"
only logs when toggling the navbar, and "Chat rendered"
only logs when typing in the chat text area.
Upvotes: 2
Reputation: 351
It looks like you are changing a global context on input field data change. If your global context is defined on a level of parent components (in relation to your input component), then the parent and all children will have to re-render. You have several options to avoid this behavior:
Upvotes: 1
Reputation: 373
I recommend use jotai or other state management libraries.
Context is not suitable for high-frequency changes.
And, the RightBar
's state looks can separate to other hook/context.
There is tricky one solution solve some render problems: https://codesandbox.io/s/stoic-mclaren-x6yfv?file=/src/context/ChatContext.js
Your code needs to be refactored, and useChatService
in ChatSection
also depends on your useChat
, so ChatSection
will re-render when the text changes.
Upvotes: 1