[name|Friend], when you see a useEffect
that updates a useState
and returns a value, you might be looking for a useSyncExternalStore
. This is my current vendetta.
Makes it easier to fix jank like this:

PS: you can read and share this online
A common pattern
A pattern I see a lot in our React code combines a state, an effect, and a subscription:
function useSomeValue() {
const [value, setValue] = useState(0)
useEffect(() => {
const eventSource = getEventSource()
eventSource.subscribe((val) => setValue(val))
return () => {
eventSource.unsubscribe()
}
}, [])
return value
}
This is a custom hook that subscribes to an event source like a browser API, or a ResizeObserver, or a state machine. Sometimes includes refs to the DOM to measure things.
This works.
The effect runs on mount, subscribes to a thing, updates state to trigger re-renders, and cleans up with an unsubscribe when the component unmounts. It's a pattern you're familiar with after writing React for a while and you easily spot what's happening.
Can lead to jank with server rendering
The problem is that React has to render your component 2+ times before it settles into what you wanted. First it renders with a default value, then the effect runs, then it re-renders when state updates.
What you saw in the gif above is a slow hydration process.
- Component rendered on server with default values
- Couldn't subscribe to browser events because there's no browser (I haven't confirmed if effects run at all)
- HTML showed up in the browser
- Hydration ran to make everything interactive
- Finally the effect ran on mount
- Subscribed to browser event
- Updated state
- And rendered the component

Look at all that JavaScript compute chugging away :D It's not a data issue, notice there's no network calls on that graph. We preload data with a shared query cache during server rendering.
useSyncExternalStore to the rescue
The right way to do this effect+subscribe+state pattern is a useSyncExternalStore. This took me a long time to grok but it's super neat. The API is cleaner and you can specify a server-side default value.
Like this
const eventSource = getEventSource()
function subscribe(callback) {
eventSource.onChange(callback)
return () => {
eventSource.unsubscribe(callback)
}
}
function useSomeValue() {
const value = useSyncExternalStore(
subscribe,
() => eventSource.currentValue(),
() => defaultValue
)
return value
}
We now have an explicit subscribe
function that executes a callback when the value changes. This runs our value getter – the 2nd param to useSyncExternalStore
. Last param is a default value getter that runs during server rendering.
You could, for example, initiate a ResizeObserver in your subscribe function, then measure a ref
as your value getter.
The result is a less janky app.

Now you just gotta figure out how to set the right default values to minimize jank.
Cheers,
~Swizec
Continue reading about You may be looking for a useSyncExternalStore
Semantically similar articles hand-picked by GPT-4
- Update state during render, better explained
- useCallback is a code smell
- useReducer + useContext for easy global state without libraries
- React can update state during render
- Trying the new ResizeObserver and IntersectionObserver APIs
Learned something new?
Read more Software Engineering Lessons from Production
I write articles with real insight into the career and skills of a modern software engineer. "Raw and honest from the heart!" as one reader described them. Fueled by lessons learned over 20 years of building production code for side-projects, small businesses, and hyper growth startups. Both successful and not.
Subscribe below 👇
Software Engineering Lessons from Production
Join Swizec's Newsletter and get insightful emails 💌 on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.
"Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"
Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.
Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.
Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.
Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev
Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization
Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections
Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog
Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com
By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️