Suggest an editImprove this articleRefine the answer for “Refs in React (useRef, createref, forwardref)”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Refs in React** give direct access to a DOM node without triggering re-renders. `useRef` persists across renders (functional components), `createRef` creates a new object each render (class components only), `forwardRef` passes a ref into a child component. ```jsx const Input = forwardRef((props, ref) => <input ref={ref} {...props} />); function Parent() { const inputRef = useRef(null); return <Input ref={inputRef} />; } ``` **Key rule:** DOM access or mutable non-UI data? Use ref. UI updates? Use state.Shown above the full answer for quick recall.Answer (EN)Image**Refs in React** are objects that hold a direct reference to a DOM node or component instance, outside of React's rendering cycle. ## Theory ### TL;DR - A ref is a direct phone line to a DOM element: you bypass the rendering queue and call it straight - `useRef` returns the same `{ current }` object on every render. Changing `.current` does not trigger a re-render - `createRef` allocates a new object on every call, so it only makes sense in class component constructors - `forwardRef` lets a parent pass its ref down into a custom child component - `useImperativeHandle` controls what the parent can actually access through that ref - Rule: need `focus()`, `play()`, an interval ID, or a previous value? Use ref. Need to update the UI? Use state ### Quick example ```jsx import { useRef } from 'react'; function VideoPlayer() { const videoRef = useRef(null); // starts as { current: null } const play = () => { videoRef.current.play(); // direct DOM call, no re-render }; return ( <div> <video ref={videoRef} src="demo.mp4" /> <button onClick={play}>Play</button> </div> ); } ``` After mount, `videoRef.current` points to the real `<video>` element. The button calls the native `video.play()` API without any state update. ### Key difference from state State changes go through React's virtual DOM diffing and schedule a re-render. A ref just holds `.current`, a plain mutable property React does not watch. You write to it, React ignores it, nothing re-renders. React sets `ref.current` during the commit phase, after it finishes updating the real DOM. This makes refs the right tool when you need to talk to the DOM directly, or store mutable data that has no effect on what the user sees. ### useRef vs createRef `useRef` stores its value in the fiber node's `memoizedState` linked list. The same object comes back on every render because React does not reset hooks on updates. `createRef` allocates a fresh `{ current: null }` every time it runs. In a functional component that means a new object every render, and the ref is never properly attached. ```jsx // Functional component const ref = useRef(null); // same object, every render const ref = createRef(); // new object, every render - always null ``` `createRef` belongs in class component constructors, where it runs exactly once. ### When to use refs - **Focus and text selection**: `inputRef.current.focus()` after a modal opens or a keyboard shortcut fires - **Media and canvas**: `videoRef.current.play()`, `canvasRef.current.getContext('2d')` for frame-by-frame drawing - **Mutable flags without render**: whether autoplay succeeded, whether the user has scrolled past a section, a previous prop value - **Timer IDs**: store the `setInterval` return value so you can call `clearInterval` in `useEffect` cleanup - **Third-party libraries**: Chart.js, Video.js, any canvas-based tool that takes a real DOM node at init time I've seen forms where pagination state was kept in a ref to avoid extra renders. It worked fine until the component needed to display the current page number, and suddenly it was always 1. If the value affects what the user sees, it belongs in state. ### API comparison | | `useRef` | `createRef` | `forwardRef` | |---|---|---|---| | Where to use | Functional components | Class components | Any component exposing a ref to its parent | | Persists across renders | Yes | No | Depends on inner `useRef` | | Triggers re-render | No | No | No | | Typical use | DOM access, mutable values | DOM access in class components | Passing ref through component boundary | | When to use | Almost always | Only in class constructors | When parent needs direct access to child DOM | ### forwardRef By default you cannot put a `ref` prop on a custom component. React swallows it silently. `forwardRef` wraps the component and passes the ref as a second argument to the render function: ```jsx import { forwardRef, useRef } from 'react'; const CustomInput = forwardRef((props, ref) => { return <input ref={ref} {...props} />; }); function Parent() { const inputRef = useRef(null); return ( <div> <CustomInput ref={inputRef} placeholder="Type here" /> <button onClick={() => inputRef.current.focus()}>Focus</button> </div> ); } ``` Now `inputRef.current` is the real `<input>`. The parent can call any DOM method on it. ### useImperativeHandle Total DOM access is sometimes more than you want to hand over. `useImperativeHandle` lets you define exactly what the parent receives: ```jsx import { forwardRef, useRef, useImperativeHandle } from 'react'; const Video = forwardRef((props, ref) => { const videoRef = useRef(null); useImperativeHandle(ref, () => ({ playPause: () => videoRef.current.paused ? videoRef.current.play() : videoRef.current.pause(), }), []); return <video ref={videoRef} {...props} />; }); function Player() { const videoRef = useRef(null); return ( <> <Video ref={videoRef} src="demo.mp4" /> <button onClick={() => videoRef.current.playPause()}>Toggle</button> </> ); } ``` The parent gets `playPause` and nothing else. The raw DOM node stays inside the child. This pattern is standard in component libraries like React Player. ### How React attaches refs React sets `ref.current` during the commit phase, after it finishes writing to the real DOM. Before mount, `ref.current` is `null`. After unmount, React resets it to `null` again. So reading `ref.current` during render always gives you a stale or null value. Read it inside `useEffect` or event handlers, never in the render body. In React 18 with StrictMode, components mount twice in development. `useRef` survives both mounts because it lives in the same fiber object. But effects clean up and run twice. If you store an observer or a subscription in a ref, clean it up in the `useEffect` return function or you get a leak. ### Common mistakes 1. **Reading `ref.current` during render** ```jsx // Wrong - ref.current is null here, element does not exist yet function Component() { const ref = useRef(null); console.log(ref.current); // null return <div ref={ref}>text</div>; } // Correct useEffect(() => { console.log(ref.current); // real DOM node }, []); ``` 2. **Storing UI state in a ref** ```jsx // Wrong - display goes stale, no re-render happens const valueRef = useRef(''); valueRef.current = e.target.value; // Correct const [value, setValue] = useState(''); setValue(e.target.value); ``` 3. **`createRef` inside a functional component** ```jsx // Wrong - new ref every render, ref never sticks function Bad() { const ref = createRef(); return <input ref={ref} />; } // Correct function Good() { const ref = useRef(null); return <input ref={ref} />; } ``` 4. **Missing null check before using `ref.current`** ```jsx // Wrong - crashes if element is not yet mounted inputRef.current.focus(); // Correct inputRef.current?.focus(); ``` 5. **`forwardRef` without `useImperativeHandle` exposes the entire DOM node** The parent can read `.value`, call `.blur()`, modify `.style` directly. For a simple internal component that is usually fine. For a component in a shared library or design system, always limit the surface with `useImperativeHandle`. ### Real-world usage - **React Aria (Adobe)**: `useRef` for focus management in accessible modals and dropdowns - **React Hook Form**: stores input refs for validation without re-rendering on every keystroke (10M+ weekly npm downloads) - **Recharts**: `forwardRef` lets parent components attach resize observers to chart containers - **Framer Motion**: refs drive imperative scroll and animation triggers - **Video.js**: passes ref to a `<canvas>` element for WebGL overlay rendering ### Follow-up questions **Q:** What is the difference between `useRef` and `createRef`? **A:** `useRef` stores its object in the fiber's hook state and returns the same reference every render. `createRef` allocates a new `{ current: null }` on every call. In a functional component that means the ref resets every render and is never usefully attached. **Q:** When does `ref.current` get set? **A:** During the commit phase, after React has updated the real DOM. It is `null` before mount and resets to `null` after unmount. Never read it during render. **Q:** Why combine `useImperativeHandle` with `forwardRef`? **A:** To expose a controlled API instead of the full DOM node. The parent gets only the methods you define, which prevents accidental direct DOM manipulation from outside the component boundary. **Q:** Can refs cause memory leaks? **A:** Yes. If you store an interval ID, observer, or event listener in a ref and never clear it in `useEffect` cleanup, it stays alive after the component unmounts. Always return a cleanup function. **Q:** How do refs behave in concurrent React 18? **A:** Refs attach after `commitRoot` finishes all work. Time slicing does not affect ref stability because refs live outside the fiber mutation phase. A low-priority suspended render does not reset `ref.current`. The attachment order is deterministic regardless of how many times rendering is interrupted. ## Examples ### Autoplay detection ```jsx import { useRef, useEffect } from 'react'; function AutoplayVideo({ src }) { const videoRef = useRef(null); const wasPlayingRef = useRef(false); // mutable flag, no re-render needed useEffect(() => { const video = videoRef.current; video .play() .then(() => { wasPlayingRef.current = true; // stored without triggering update }) .catch(() => { console.log('Autoplay blocked by browser policy'); }); }, [src]); return <video ref={videoRef} src={src} muted />; } ``` Chrome blocks autoplay on unmuted video, so the `muted` attribute is required. `wasPlayingRef` stores the result without triggering a re-render because the autoplay status does not need to appear in the UI. This is a common pattern in production video players. ### Custom hook for previous value ```jsx import { useRef, useEffect, useState } from 'react'; function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; // runs after render, so ref holds last render's value }, [value]); return ref.current; } function Counter() { const [count, setCount] = useState(0); const prevCount = usePrevious(count); return ( <div> <p>Now: {count}, before: {prevCount}</p> <button onClick={() => setCount(count + 1)}>+</button> </div> ); } ``` The `useEffect` runs after render. So when the component returns, `ref.current` still holds the previous render's value. On the next render, that old value is what gets returned. The timing asymmetry is the whole mechanism. ### Video player with controlled imperative API ```jsx import { forwardRef, useRef, useImperativeHandle } from 'react'; const VideoPlayer = forwardRef(({ src }, ref) => { const videoRef = useRef(null); useImperativeHandle(ref, () => ({ play: () => videoRef.current.play(), pause: () => videoRef.current.pause(), seek: (seconds) => { videoRef.current.currentTime = seconds; }, }), []); return <video ref={videoRef} src={src} />; }); function App() { const playerRef = useRef(null); return ( <> <VideoPlayer ref={playerRef} src="demo.mp4" /> <button onClick={() => playerRef.current.play()}>Play</button> <button onClick={() => playerRef.current.seek(30)}>Skip 30s</button> </> ); } ``` The parent has `play`, `pause`, and `seek`. It cannot read `.currentTime`, `.buffered`, or `.duration`. That controlled boundary is exactly what `useImperativeHandle` is for.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.