This error occurs when you try to access ref.current during the render phase of a component using forwardRef, before React has attached the ref to a DOM node. Refs are only populated after the component mounts and the DOM element is created.
This error happens when you attempt to read or write `ref.current` during the render phase of a React component that uses `forwardRef`. React refs follow a specific lifecycle: they are `null` or `undefined` until after the component has rendered and React has attached the ref to the actual DOM node. When using `forwardRef`, the ref parameter is passed to your component, but it won't have a `.current` value until React completes the render and commit phases. Accessing `ref.current` during rendering breaks React's expectation that components behave like pure functions, making the component's output unpredictable. React cannot track changes to `ref.current` during rendering, which means it won't know when to re-render your component. This is by design - refs are meant for accessing imperative DOM APIs or storing mutable values that don't affect rendering.
The most common fix is to move any ref.current access into a useEffect hook, which runs after the component renders and the ref is attached:
import React, { forwardRef, useEffect } from 'react';
const MyInput = forwardRef<HTMLInputElement>((props, ref) => {
// ❌ WRONG: Accessing ref.current during render
// console.log(ref.current); // Error!
useEffect(() => {
// ✅ CORRECT: Access ref.current after render
if (ref && typeof ref !== 'function' && ref.current) {
console.log('Input element:', ref.current);
ref.current.focus(); // Safe to call DOM methods
}
}, [ref]);
return <input ref={ref} {...props} />;
});For immediate DOM manipulation before paint, use useLayoutEffect instead of useEffect.
Always check that the ref exists and has a .current property before accessing it:
import React, { forwardRef, useEffect } from 'react';
const CustomComponent = forwardRef<HTMLDivElement>((props, ref) => {
useEffect(() => {
// Check if ref is an object with current property
if (ref && typeof ref !== 'function' && ref.current) {
// Safe to access ref.current here
const element = ref.current;
element.style.backgroundColor = 'lightblue';
}
}, [ref]);
return <div ref={ref}>Content</div>;
});TypeScript users can use optional chaining for cleaner code: ref?.current.
If you need to read DOM properties (like dimensions) before the browser paints, use useLayoutEffect:
import React, { forwardRef, useLayoutEffect, useState } from 'react';
const MeasuredComponent = forwardRef<HTMLDivElement>((props, ref) => {
const [height, setHeight] = useState(0);
useLayoutEffect(() => {
if (ref && typeof ref !== 'function' && ref.current) {
// Measure DOM before browser paints
const rect = ref.current.getBoundingClientRect();
setHeight(rect.height);
}
}, [ref]);
return (
<div ref={ref}>
Height: {height}px
{props.children}
</div>
);
});Note: useLayoutEffect blocks browser painting, so use it only when necessary.
When the element attached to the ref is conditionally rendered, the ref may remain null even inside effects:
import React, { forwardRef, useEffect } from 'react';
interface Props {
show: boolean;
}
const ConditionalInput = forwardRef<HTMLInputElement, Props>(
({ show }, ref) => {
useEffect(() => {
if (ref && typeof ref !== 'function' && ref.current) {
ref.current.focus();
}
// ref.current will be null if show is false
}, [ref, show]); // Include show in dependencies
// Only render input when show is true
if (!show) return null;
return <input ref={ref} placeholder="Type here..." />;
}
);The ref will only have a .current value when the element is actually rendered.
Callback refs give you more control over when and how refs are set:
import React, { forwardRef, useCallback } from 'react';
const SmartInput = forwardRef<HTMLInputElement>((props, forwardedRef) => {
const handleRef = useCallback(
(node: HTMLInputElement | null) => {
// Called when element mounts
if (node) {
console.log('Element mounted:', node);
node.focus();
}
// Forward the ref to parent
if (typeof forwardedRef === 'function') {
forwardedRef(node);
} else if (forwardedRef) {
forwardedRef.current = node;
}
},
[forwardedRef]
);
return <input ref={handleRef} {...props} />;
});Callback refs are called immediately when the element is attached or detached.
React 19 Update: In React 19, forwardRef is no longer necessary. You can pass ref as a regular prop:
// React 19
function MyInput({ ref }) {
return <input ref={ref} />;
}Fallback Ref Pattern: If you want a component to have its own internal ref even when no ref is forwarded, use a fallback pattern:
import React, { forwardRef, useRef } from 'react';
const ComponentWithFallback = forwardRef<HTMLDivElement>((props, forwardedRef) => {
const fallbackRef = useRef<HTMLDivElement>(null);
const ref = forwardedRef || fallbackRef;
// Now ref will always have a value, even if parent doesn't pass one
return <div ref={ref as React.RefObject<HTMLDivElement>}>Content</div>;
});useImperativeHandle: When using forwardRef with useImperativeHandle, remember that the imperative methods won't be available until after the component mounts:
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
interface CustomHandle {
focus: () => void;
}
const CustomInput = forwardRef<CustomHandle>((props, ref) => {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current?.focus();
},
}));
return <input ref={inputRef} />;
});
// Parent usage
function Parent() {
const ref = useRef<CustomHandle>(null);
useEffect(() => {
// Safe to call after mount
ref.current?.focus();
}, []);
return <CustomInput ref={ref} />;
}Performance Consideration: Accessing refs doesn't trigger re-renders, which is why you can't use ref.current as a dependency in rendering logic. If you need a value that affects rendering, use useState instead.
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite