forwardRef: exposing a ref through a component
refs aren't passed like a normal prop. If you want a parent component to
get a reference to a node inside a child component, that child must
forward the ref with React.forwardRef:
const Input = React.forwardRef(function Input(props, ref) {
return <input ref={ref} {...props} />;
});
function Form() {
const inputRef = React.useRef(null);
// inputRef.current will point to the child's real <input>
return <Input ref={inputRef} />;
}
useImperativeHandle: customizing what the ref exposes
Sometimes you don't want to give access to the entire DOM node, but only to a few
specific actions (a small imperative API). useImperativeHandle defines exactly what
the parent will see through the ref:
const Field = React.forwardRef(function Field(props, ref) {
const inputRef = React.useRef(null);
React.useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus(),
clear: () => { inputRef.current.value = ""; },
}));
return <input ref={inputRef} />;
});
// The parent can call fieldRef.current.focus() — and nothing else.
It's an exceptional pattern: use it only for real imperative actions (focus, scroll, play/pause). For data, keep preferring props and state.
Concurrent hooks: keeping the UI responsive
Modern React can interrupt and prioritize renders. Two hooks take advantage of this so that expensive work doesn't freeze the interface:
useTransition
Marks a state update as non-urgent (a transition). React
processes it with low priority, letting urgent interactions (typing
in an input) respond instantly. It also gives you an isPending to show a
loading indicator:
const [isPending, startTransition] = useTransition();
function search(text) {
setText(text); // urgent: the input updates now
startTransition(() => {
setResults(filter(text)); // non-urgent: can wait/be interrupted
});
}
useDeferredValue
Takes a value and returns a "deferred" version of it. While a new value arrives, React can keep showing the previous one, avoiding recalculating a heavy list on every keystroke:
const deferredQuery = useDeferredValue(query);
// The expensive list is filtered with deferredQuery, not with query.
Both pursue the same goal: making typing or clicking feel fluid even though there's an expensive render in the background.
useId: unique and stable identifiers
React.useId generates a unique, stable id for the component, identical on the
server and the client (key to avoiding hydration errors). Its star
use is accessibility: linking a <label> with its <input>.
function Field() {
const id = React.useId();
return (
<div>
<label htmlFor={id}>Name</label>
<input id={id} />
</div>
);
}
This way you don't need to invent ids by hand (which would collide if the component is used
several times on the page). Never use useId to generate list keys:
it's for DOM element ids.
In the code exercise you'll practice
React.useId(synchronous and validatable). The concurrent hooksuseTransitionanduseDeferredValueare evaluated in the quizzes, since their effect depends on React's asynchronous scheduler.