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 synchronously after DOM mutations but before the browser paints the screen, so the position adjustment happens before the user ever sees the unadjusted position; useEffect runs asynchronously after paint, which could cause a visible flicker/jump
javascript
function SearchResults() {
const [isPending, startTransition] = useTransition();
const [query, setQuery] = useState('');
const handleChange = e => {
const value = e.target.value;
setQuery(value); // urgent
startTransition(() => {
setResults(expensiveSearch(value)); // non-urgent
});
};
}AisPending is true while the startTransition update is still being processed in the background, letting the UI show a loading indicator; setQuery is kept outside startTransition because the input field itself must update immediately for a responsive typing experience — only the expensive derived work (expensiveSearch) is deferred
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.