Avoiding stale state in React useEffect


Sometimes it's easy for me to forget I'm using JavaScript when building React apps.
I've been using Lit for a long time. In Lit, it's plainly clear that I'm using JavaScript and working with the DOM. In React, that gets abstracted away. But it doesn't change the fact that the rules of JavaScript still apply.
Why is my state getting reset on every useEffect call in React?
I'm building a chat application for an upcoming Hilla presentation. I want to subscribe to incoming messages through a web socket, and I only want to set up the connection once when the component gets mounted.
This was my initial attempt:
export function ChatView() {
const [messages, setMessages] = useState<Message[]>([]);
useEffect(() => {
const subscription = ChatService
.join()
.onNext(message =>
setMessages([...messages, message]));
return () => {
subscription.cancel();
};
}, []);
return (
// view code
)
}
Looks straightforward enough: subscribe to the service, update the messages array on incoming messages, and close the subscription on unmount. Only run the effect once by passing in an empty deps array []
.
The problem? My message list only contained the latest message instead of the full chat history.
After a few minutes of debugging, it dawned on me: because the effect only runs once, the value of messages
in the closure is still the value it had when the component was created.
Updating state from a useEffect hook
The solution was simple. To update a state that's dependent on the previous value of the state, you need to do a functional update by passing in a function to setState. The function gets the previous state as a parameter, which avoids the stale state issue.
export function ChatView() {
const [messages, setMessages] = useState<Message[]>([]);
useEffect(() => {
const subscription = ChatService
.join()
.onNext(message =>
setMessages(prev => [...prev, message]));
return () => {
subscription.cancel();
};
}, []);
return (
// view code
)
}
That's it!
You can find the full code for my demo on my GitHub https://github.com/marcushellberg/hilla-react-chat
Subscribe to my newsletter
Read articles from Marcus Hellberg directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Marcus Hellberg
Marcus Hellberg
Marcus is a long-time Java and web developer. He's always curious to learn new technologies and excited to share his insights with others. He is the VP of Developer Relations at Vaadin and an international speaker who has presented at over 100 events worldwide.