codejump

Advanced Hook Patterns — Series 2

Preview — 3 of 10 questions

What problem does useSyncExternalStore solve that a manual useState + useEffect subscription does not fully solve?

javascript
import { useSyncExternalStore } from 'react';

function useWindowWidth() {
  return useSyncExternalStore(
    callback => {
      window.addEventListener('resize', callback);
      return () => window.removeEventListener('resize', callback);
    },
    () => window.innerWidth,
  );
}
AIt's purely a performance optimization — functionally identical to useState + useEffect, just faster
BIt only works with Redux-style stores, not plain browser APIs like window
CIt correctly handles subscribing to external (non-React) state sources — like browser APIs or third-party stores — and guarantees tearing-free reads even under React 18's concurrent rendering; a manual useState/useEffect subscription can read stale or inconsistent values during a concurrent render
DIt replaces useState entirely — useState should never be used once useSyncExternalStore is available

Why is useLayoutEffect used here instead of useEffect?

javascript
function Tooltip() {
  const ref = useRef(null);
  useLayoutEffect(() => {
    const { height } = ref.current.getBoundingClientRect();
    ref.current.style.top = `-${height}px`;
  }, []);
  return <div ref={ref}>Tooltip</div>;
}
AuseLayoutEffect is simply a faster, more modern replacement for useEffect in all cases
BuseLayoutEffect only works together with useRef — useEffect doesn't support refs at all
CThere's no real difference — this code would behave identically with useEffect
DuseLayoutEffect runs after DOM mutations and blocks the next paint until its work and resulting state updates finish; useEffect does not guarantee this, so a layout adjustment there could visibly flicker

What does startTransition defer here, and what still runs immediately?

javascript
function SearchResults() {
  const [isPending, startTransition] = useTransition();
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  const handleChange = e => {
    const value = e.target.value;
    setQuery(value); // urgent
    startTransition(() => {
      setResults(expensiveSearch(value)); // non-urgent
    });
  };

  return <>
    <input value={query} onChange={handleChange} />
    {isPending && <span>Updating…</span>}
    <Results items={results} />
  </>;
}
AsetResults is marked as a non-urgent update and isPending can report that transition; expensiveSearch(value) still runs immediately inside the callback, so a slow search can block the input handler
BisPending is always false unless an error occurs during rendering
CBoth setQuery and setResults should be inside startTransition — keeping them separate is a bug
DisPending tracks whether a network request is currently in flight, unrelated to state updates

Sign up free to play

Answer all 10 questions (7 more), see explanations for every answer, and track your score.