I am trying to utilize React.forwardRef()
and I'm scratching my head over the following issue:
It appears that IntelliJ is able to correctly interfer the type of the reference, which is FilterRef
in this case.
However, I am unable to access the current value ref.current
without a compilation error stating that this would not exist.
Printing it out at runtime, of course, confirms that current
is an existing attribute.
Not sure what causes this issue.
I am creating a reference which I want to share with a Filter
component and a context-provider:
const filterRef = useRef<FilterElement>({ filters, setFilters: onSetFilters });
console.log('filterRef', filterRef);
return (
<>
<Filter ref={filterRef} />
<FilterContext.Provider value={{ ref: filterRef }}>
<Outlet />
</FilterContext.Provider>
</>
);
However, current
is always null
:
export const Filter = React.forwardRef<FilterElement, FilterProps>((props, ref) => {
const filterRef = useRef<FilterElement>(null);
useImperativeHandle(ref, () => filterRef.current!, []);
console.log('filter', filterRef, ref);
// ...
}
Edit 2
In order to have a separate state being managed by Filter
itself, the following can be done as well:
export const Filter = React.forwardRef<FilterElement | undefined, FilterProps>((props, ref) => {
const [filters, setFilters] = useState<FilterItem[]>([]);
const refInstance: FilterElement = useMemo(() => {
return {
filters,
setFilters,
};
}, [filters]);
useImperativeHandle(ref, () => refInstance, [refInstance]);
// ...
}
Edit:
Based on the updated code, I think you don't need the ref
at all, as you can just pass filters
and setFilters
to both the Filter
component as well as the FilterContext
provider:
<Filter filters={ filters } setFilters={ onSetFilters } />
<FilterContext.Provider value={{ filters, setFilters: onSetFilters }}>
<Outlet />
</FilterContext.Provider>
In any case, if you want to do that, you don't need to use forwardRef
, just name your prop something else and type it as RefObject
:
const App = () => {
const filterRef = useRef<FilterElement>({ filters, setFilters: onSetFilters });
return (
<>
<Filter filterRef={filterRef} />
<FilterContext.Provider value={{ ref: filterRef }}>
<Outlet />
</FilterContext.Provider>
</>
);
}
export interface FilterProps {
filterRef: RefObject<FilterElement>;
}
export const Filter: React.FC<FilterProps> = ({ filterRef }) => {
console.log('filter', filterRef);
// ...
}
The answer below with useImperativeHandler
would be used if you want to use a ref
inside the Filter
component, but also want to expose that same ref
to the parent.
Original answer:
Refs can either be an object with a current
property, like the ones returned by useRef
or a ref
callback function, so the compiler is right.
You should create a new ref
in your Filter
component and use useImperativeHandle
to sync it with the forwarded one:
export const Filter = forwardRef<FilterRef, FilterProps>((props, ref) => {
const filterRef = useRef(null);
useImperativeHandle(ref, () => filterRef.current!, []);
...
});