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

Semantically similar articles hand-picked by GPT-4

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 👇

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. 👌"

~ Ashish Kumar

[

Senior Engineer Mindset cover

](https://swizec.com/senior-mindset/)

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

Главная - Вики-сайт
Copyright © 2011-2025 iteam. Current version is 2.146.0. UTC+08:00, 2025-10-03 04:28
浙ICP备14020137号-1 $Гость$