All quizzesHard
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.