Swizec Teller - a geek with a hatswizec.com

Senior Mindset Book

Get promoted, earn a bigger salary, work for top companies

Senior Engineer Mindset cover
Learn more

    You may be looking for a useSyncExternalStore

    [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:

    Flash of no visualization

    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.

    1. Component rendered on server with default values
    2. Couldn't subscribe to browser events because there's no browser (I haven't confirmed if effects run at all)
    3. HTML showed up in the browser
    4. Hydration ran to make everything interactive
    5. Finally the effect ran on mount
    6. Subscribed to browser event
    7. Updated state
    8. And rendered the component
    Hydration can be slow

    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.

    Less jank, visualizations show up

    Now you just gotta figure out how to set the right default values to minimize jank.

    Cheers,
    ~Swizec

    Published on September 23rd, 2025 in React, JavaScript, Frontend Web

    Did you enjoy this article?

    Continue reading about You may be looking for a useSyncExternalStore

    Semantically similar articles hand-picked by GPT-4

    Senior Mindset Book

    Get promoted, earn a bigger salary, work for top companies

    Learn more

    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 ❤️

    Created by Swizec with ❤️