This React error occurs when the first parameter passed to useImperativeHandle is not a valid ref object. Common causes include using string refs (deprecated), passing a function instead of a ref, not using forwardRef in React 18 and earlier, or ref initialization issues in React Native.
The useImperativeHandle hook allows a parent component to call methods on a child component's ref. For this to work, React requires a valid ref object as the first parameter. A valid ref object is created with useRef() or createRef() and contains a current property that React can mutate. This error occurs when React detects that the first argument to useImperativeHandle is not a proper ref object. This can happen for several reasons: using deprecated string refs, passing callback functions instead of ref objects, forgetting to use forwardRef in React versions before 19, or encountering ref initialization bugs in React Native. The error is particularly common when migrating from class components to function components, when upgrading React versions, or when working with third-party libraries that use refs internally. Understanding ref mechanics is essential for proper use of useImperativeHandle, which is often used for exposing imperative APIs from custom components.
The first parameter to useImperativeHandle must be a ref object, not a string or function:
import { useRef, useImperativeHandle } from 'react';
// ✅ CORRECT - using useRef
function MyComponent({ ref }) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus(),
}), []);
return <input ref={inputRef} />;
}
// ❌ WRONG - string ref (deprecated)
function MyComponent({ ref }) {
useImperativeHandle('myRef', () => ({ // Invalid!
focus: () => {},
}), []);
return <input />;
}
// ❌ WRONG - callback function instead of ref object
function MyComponent({ ref }) {
useImperativeHandle(() => {}, () => ({ // Invalid!
focus: () => {},
}), []);
return <input />;
}Always create refs with useRef(null) for function components or createRef() for class components.
In React 18 and earlier, you need forwardRef to receive the ref prop:
import { forwardRef, useImperativeHandle, useRef } from 'react';
// ✅ CORRECT - using forwardRef (React 18 and earlier)
const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus(),
scrollIntoView: () => inputRef.current.scrollIntoView(),
}), []);
return <input ref={inputRef} {...props} />;
});
// Parent component usage
function Parent() {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current?.focus();
};
return (
<>
<MyInput ref={inputRef} />
<button onClick={handleClick}>Focus input</button>
</>
);
}
// React 19 allows ref as regular prop (no forwardRef needed)
// function MyInput({ ref, ...props }) {
// useImperativeHandle(ref, () => ({ ... }), []);
// return <input {...props} />;
// }Check your React version: React 19+ allows ref as a regular prop, while React 18 and earlier require forwardRef.
In React Native, especially version 0.78+, refs with TypeScript generics can become false instead of objects:
// ❌ Problematic in React Native 0.78+
import { useRef } from 'react';
import { BottomSheet } from '@gorhom/bottom-sheet';
function MyComponent() {
// This can become false instead of a ref object
const sheetRef = useRef<BottomSheet>(null);
useImperativeHandle(sheetRef, () => ({ // Error: ref is false!
snapToIndex: (index: number) => {},
}), []);
return <BottomSheet ref={sheetRef} />;
}
// ✅ Solution: Remove TypeScript generic or use different approach
function MyComponent() {
const sheetRef = useRef(null); // No generic
useImperativeHandle(sheetRef, () => ({
snapToIndex: (index: number) => {
// Call methods on the actual ref
sheetRef.current?.snapToIndex(index);
},
}), []);
return <BottomSheet ref={sheetRef} />;
}If removing the generic doesn't work, try accessing the ref differently or check for React Native version compatibility issues.
Ensure the parent component creates and passes a valid ref:
// ✅ CORRECT parent usage
function Parent() {
const childRef = useRef(null); // Create ref with useRef
useEffect(() => {
// Can call methods exposed by child
childRef.current?.someMethod();
}, []);
return <ChildComponent ref={childRef} />;
}
// ❌ WRONG - not creating a ref
function Parent() {
return <ChildComponent ref={null} />; // Passing null directly
}
// ❌ WRONG - using string ref
function Parent() {
return <ChildComponent ref="childRef" />; // String ref (deprecated)
}
// ❌ WRONG - callback ref without proper handling
function Parent() {
const handleRef = (node) => {
// Callback refs don't work with useImperativeHandle
};
return <ChildComponent ref={handleRef} />;
}The parent must use useRef(null) or createRef() and pass the ref object (not the .current property).
Add debugging to inspect the ref value:
function MyComponent({ ref }) {
console.log('Ref type:', typeof ref);
console.log('Ref value:', ref);
console.log('Ref has current property?', ref && 'current' in ref);
useImperativeHandle(ref, () => ({
focus: () => {},
}), []);
return <div />;
}
// Or add a useEffect to check ref after mount
function MyComponent({ ref }) {
useEffect(() => {
console.log('Ref after mount:', ref);
console.log('Ref.current:', ref?.current);
}, [ref]);
useImperativeHandle(ref, () => ({
focus: () => {},
}), []);
return <div />;
}A valid ref object should:
- Be an object (not string, function, null, undefined, or false)
- Have a current property
- The current property can be initially null (will be set by React)
If ref is false, undefined, or a function, you need to fix how it's created or passed.
Multiple React copies can cause ref context issues:
# Check for duplicate React installations
npm ls react
npm ls react-dom
# If multiple versions exist, deduplicate
npm dedupe
# Or add resolutions/overrides in package.json
{
"overrides": {
"react": "18.2.0",
"react-dom": "18.2.0"
}
}Also check webpack/vite config for proper React resolution:
// webpack.config.js
module.exports = {
resolve: {
alias: {
react: path.resolve(__dirname, 'node_modules/react'),
'react-dom': path.resolve(__dirname, 'node_modules/react-dom')
}
}
};Multiple React instances are common with npm link, monorepos, or conflicting dependencies.
If ref problems continue, consider these alternatives:
1. Use callback ref pattern (without useImperativeHandle):
function MyInput({ onRef }) {
const inputRef = useRef(null);
useEffect(() => {
onRef?.({
focus: () => inputRef.current.focus(),
});
}, [onRef]);
return <input ref={inputRef} />;
}
// Parent usage
function Parent() {
const [inputApi, setInputApi] = useState(null);
useEffect(() => {
inputApi?.focus();
}, [inputApi]);
return <MyInput onRef={setInputApi} />;
}2. Use context for complex imperative APIs:
const InputContext = createContext(null);
function MyInput({ children }) {
const inputRef = useRef(null);
const api = useMemo(() => ({
focus: () => inputRef.current.focus(),
}), []);
return (
<InputContext.Provider value={api}>
<input ref={inputRef} />
{children}
</InputContext.Provider>
);
}
// Child can consume context
function FocusButton() {
const inputApi = useContext(InputContext);
return <button onClick={() => inputApi?.focus()}>Focus</button>;
}3. Re-evaluate if useImperativeHandle is necessary - often props or context are cleaner solutions.
React 19 Changes: React 19 simplifies ref handling by making ref a regular prop. This eliminates the need for forwardRef in many cases:
// React 19 - ref is a regular prop
function MyInput({ ref, ...props }) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus(),
}), []);
return <input ref={inputRef} {...props} />;
}
// No forwardRef needed in parent either
function Parent() {
const inputRef = useRef(null);
return <MyInput ref={inputRef} />;
}TypeScript with forwardRef: When using TypeScript with forwardRef, ensure proper typing:
import { forwardRef, useImperativeHandle, useRef, ForwardedRef } from 'react';
interface InputHandle {
focus: () => void;
scrollIntoView: () => void;
}
interface InputProps {
label: string;
}
const MyInput = forwardRef<InputHandle, InputProps>(
function MyInput({ label }, ref: ForwardedRef<InputHandle>) {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
scrollIntoView: () => inputRef.current?.scrollIntoView(),
}), []);
return <input ref={inputRef} placeholder={label} />;
}
);React Native Considerations: In React Native, ref issues are more common due to:
- Bridge communication between JavaScript and native code
- Third-party library compatibility issues
- Version mismatches between React Native and community libraries
- TypeScript generic type inference problems
When debugging React Native ref issues, also check:
- Library version compatibility matrices
- Whether the component supports ref forwarding
- If there are known issues with refs in the library's GitHub issues
Performance Note: useImperativeHandle runs on every render unless dependencies are properly specified. Always include all dependencies in the dependency array to avoid unnecessary re-creations of the imperative handle.
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