This error occurs when attempting to directly assign a value to ref.current in a forwardRef component when the ref is typed as a RefObject, which has an immutable current property. It commonly happens when mixing TypeScript types or trying to mutate refs passed from parent components.
This error occurs when you attempt to assign a value to the `current` property of a ref object that TypeScript has typed as immutable. In React, refs can be either mutable (`MutableRefObject<T>`) or immutable (`RefObject<T>`), depending on how they're created and typed. When using `forwardRef`, the ref parameter you receive can be either a callback ref or a ref object. If it's typed as `RefObject<T>`, the `current` property is readonly and cannot be directly assigned. This is a TypeScript safeguard that prevents improper ref manipulation. The error typically manifests when you're trying to assign a DOM element or value directly to `ref.current` in a component that receives a ref via `forwardRef`, especially when the parent component creates the ref with specific TypeScript types that result in an immutable RefObject.
Instead of directly assigning to ref.current, use the callback ref pattern which allows safe assignment:
import React, { forwardRef } from 'react';
interface MyComponentProps {
label: string;
}
const MyComponent = forwardRef<HTMLInputElement, MyComponentProps>(
({ label }, ref) => {
return (
<div>
<label>{label}</label>
<input
ref={ref}
type="text"
/>
</div>
);
}
);
export default MyComponent;React handles the assignment automatically when you pass the ref to the element's ref prop.
If you need a mutable ref in the parent component, change the type to include null in the union:
// ❌ Creates immutable RefObject
const inputRef = useRef<HTMLInputElement>(null);
// ✅ Creates mutable MutableRefObject
const inputRef = useRef<HTMLInputElement | null>(null);The | null addition makes TypeScript infer MutableRefObject<HTMLInputElement | null> instead of RefObject<HTMLInputElement>.
Type the ref parameter in forwardRef using ForwardedRef instead of RefObject:
import React, { forwardRef, ForwardedRef } from 'react';
interface MyComponentProps {
value: string;
}
const MyComponent = forwardRef<HTMLDivElement, MyComponentProps>(
({ value }, ref: ForwardedRef<HTMLDivElement>) => {
// If you need to manually assign (rare case)
const handleClick = () => {
if (ref && typeof ref !== 'function') {
// This works because ForwardedRef can be mutable
ref.current?.focus();
}
};
return (
<div ref={ref} onClick={handleClick}>
{value}
</div>
);
}
);
export default MyComponent;Note: You should rarely need to manually assign ref.current. Let React handle it by passing the ref to the element.
If you need to expose custom methods or properties on the ref, use useImperativeHandle:
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
interface CustomRefAPI {
focus: () => void;
clear: () => void;
}
interface InputProps {
placeholder?: string;
}
const CustomInput = forwardRef<CustomRefAPI, InputProps>(
({ placeholder }, ref) => {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current?.focus();
},
clear: () => {
if (inputRef.current) {
inputRef.current.value = '';
}
}
}));
return <input ref={inputRef} placeholder={placeholder} />;
}
);
// Usage in parent:
// const customRef = useRef<CustomRefAPI>(null);
// <CustomInput ref={customRef} />
// customRef.current?.focus();
export default CustomInput;This pattern allows you to define exactly what the parent can do with the ref without violating immutability.
If your component needs to support both callback refs and object refs, create a helper function:
import React, { forwardRef, ForwardedRef } from 'react';
function setRef<T>(
ref: ForwardedRef<T>,
value: T | null
): void {
if (typeof ref === 'function') {
ref(value);
} else if (ref) {
// Type assertion needed because ref might be readonly
(ref as React.MutableRefObject<T | null>).current = value;
}
}
interface MyComponentProps {
className?: string;
}
const MyComponent = forwardRef<HTMLDivElement, MyComponentProps>(
({ className }, ref) => {
const handleMount = (element: HTMLDivElement | null) => {
setRef(ref, element);
// Additional logic when element mounts
if (element) {
console.log('Element mounted:', element);
}
};
return <div ref={handleMount} className={className} />;
}
);
export default MyComponent;This pattern is useful when you need to perform additional actions when the ref is set.
TypeScript Type Inference:
React's useRef has three overloads that determine mutability:
- useRef<T>(initialValue: T) returns MutableRefObject<T> (always mutable)
- useRef<T>(initialValue: T | null) returns MutableRefObject<T | null> (mutable)
- useRef<T>(initialValue: null) returns RefObject<T> (immutable current property)
The third case creates an immutable ref because TypeScript assumes if you're starting with null and haven't provided a union type, you intend for React to manage the assignment.
When to Use Each Pattern:
- Use immutable refs (RefObject) for DOM elements where React sets the ref automatically
- Use mutable refs (MutableRefObject) for storing mutable values like timers, previous state, or manually managed references
- Use useImperativeHandle when you need to expose specific methods to parent components
React 18+ Considerations:
In React 18 with concurrent features, refs may be attached and detached multiple times during hydration or Suspense. Always check for null before accessing ref.current, and avoid storing critical state in refs that affects rendering.
Fast Refresh Gotcha:
In development with Next.js or Create React App, fast refresh can occasionally cause refs in forwardRef components to become empty objects, leading to "Object is not extensible" errors. This is a known bug that typically only affects development mode and resolves with a full page refresh.
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