When wrapping a forwardRef component with Redux connect(), refs fail to pass through correctly. This happens because connect() needs explicit configuration to handle ref forwarding through its higher-order component wrapper.
This error occurs when you try to use React.forwardRef() on a component that's wrapped with Redux's connect() function without proper configuration. The connect() higher-order component (HOC) creates a wrapper around your component, which intercepts the ref by default. In React-Redux v6 and later, the library changed how refs are handled - replacing the deprecated withRef option with forwardRef. When you compose connect with forwardRef incorrectly, React sees the forwardRef result (which is an object) being passed where it expects a function component, causing type errors. Additionally, the ref doesn't reach your intended component because connect() intercepts it at the wrapper level. This is a common migration issue when upgrading from React-Redux v5 (which used withRef and getWrappedInstance) to v6+ (which uses the forwardRef option). The problem affects any component that needs direct DOM access or imperative methods exposed through refs.
The primary solution is to pass { forwardRef: true } as the fourth argument to connect():
import React from 'react';
import { connect } from 'react-redux';
const MyComponent = React.forwardRef((props, ref) => {
return (
<div ref={ref}>
<h1>{props.title}</h1>
{/* component content */}
</div>
);
});
const mapStateToProps = (state) => ({
title: state.title,
});
// Add { forwardRef: true } as the fourth argument
export default connect(
mapStateToProps,
null,
null,
{ forwardRef: true }
)(MyComponent);Now the ref will be passed through to your component correctly.
With forwardRef enabled, you can now use refs normally in parent components:
import React, { useRef, useEffect } from 'react';
import MyConnectedComponent from './MyConnectedComponent';
function ParentComponent() {
const componentRef = useRef(null);
useEffect(() => {
if (componentRef.current) {
// Access the DOM element or component instance
console.log(componentRef.current);
componentRef.current.focus(); // if it's a focusable element
}
}, []);
return <MyConnectedComponent ref={componentRef} />;
}The ref now points to the actual wrapped component, not the connect() wrapper.
If you need to forward the ref to a specific inner element rather than the component root, wrap the connected component with forwardRef:
import React from 'react';
import { connect } from 'react-redux';
// Define component without forwardRef
const MyComponent = ({ title, innerRef }) => {
return (
<div>
<input ref={innerRef} type="text" />
<h1>{title}</h1>
</div>
);
};
const mapStateToProps = (state) => ({
title: state.title,
});
// Connect first, then wrap with forwardRef
const ConnectedComponent = connect(mapStateToProps)(MyComponent);
export default React.forwardRef((props, ref) => (
<ConnectedComponent {...props} innerRef={ref} />
));This pattern gives you more control over where the ref is forwarded.
If you're upgrading from React-Redux v5, replace the old withRef pattern:
Old pattern (v5):
export default connect(
mapStateToProps,
null,
null,
{ withRef: true }
)(MyComponent);
// In parent:
myComponentRef.current.getWrappedInstance().someMethod();New pattern (v6+):
export default connect(
mapStateToProps,
null,
null,
{ forwardRef: true }
)(MyComponent);
// In parent - direct access, no getWrappedInstance
myComponentRef.current.someMethod();The getWrappedInstance() call is no longer needed with the forwardRef option.
Ensure you're using the correct connect() signature with all four parameters:
connect(
mapStateToProps, // 1st: map state to props
mapDispatchToProps, // 2nd: map dispatch to props (or null)
mergeProps, // 3rd: merge function (or null)
{
forwardRef: true, // 4th: options object
// other options...
}
)(Component)If you're only using mapStateToProps, you still need to pass null for the 2nd and 3rd parameters:
connect(mapStateToProps, null, null, { forwardRef: true })(Component)TypeScript considerations: When using forwardRef with connect() in TypeScript, you may need to explicitly type your ref:
interface MyComponentProps {
title: string;
}
const MyComponent = React.forwardRef<HTMLDivElement, MyComponentProps>(
(props, ref) => {
return <div ref={ref}>{props.title}</div>;
}
);Performance note: The forwardRef option has minimal performance impact. The connect() HOC already creates a wrapper component; enabling forwardRef simply configures how refs are passed through.
React Hooks alternative: If you're building new components, consider using the useSelector and useDispatch hooks from React-Redux instead of connect(). Hooks work naturally with forwardRef:
const MyComponent = React.forwardRef((props, ref) => {
const title = useSelector(state => state.title);
const dispatch = useDispatch();
return <div ref={ref}>{title}</div>;
});This eliminates the connect() HOC entirely and provides cleaner ref forwarding.
Multiple refs: If you need to forward multiple refs or combine refs, use the useImperativeHandle hook:
const MyComponent = React.forwardRef((props, ref) => {
const localRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => localRef.current?.focus(),
getValue: () => localRef.current?.value,
}));
return <input ref={localRef} />;
});Legacy codebase migration: When migrating large codebases from React-Redux v5 to v6+, search for all instances of "withRef: true" and "getWrappedInstance()" to identify components that need updating to the forwardRef pattern.
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