Sorry if this has been brought up before! I looked all over the docs and GitHub issues but couldn't find any mention of this
Is animated(Component) where Component is functional component a known limitation?
Here's a minimal example reproducing the issue: https://codesandbox.io/s/1mp77p57l
It also gives this warning which I assume is relevant
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
But even when attempting to use a simple functional component with React.forwardRef (also done in the example above) it seems to fail silently
That used to be a limitation in React before hooks but you still need to opt in essentially. See: https://reactjs.org/docs/hooks-reference.html#useimperativehandle
This seems to work:
const FunctionalComponentWithRef = React.forwardRef(({ color }, ref) => {
const myRef = React.useRef();
React.useImperativeHandle(ref, () => ({}));
return (
<div
ref={myRef}
style={{
background: color,
width: 250,
height: 250
}}
/>
);
});
Thank you @mcernusca that does work! I am rather confused how does it work though since as far as I understand useImperativeHandle we are basically doing nothing? (Not modifying the ref at all since we return an empty object)
Also why do we need to create and pass our own Ref that as far as I can tell is not used by anything?
All great questions.. actually even this works:
const FunctionalComponentWithRef = React.forwardRef(({ color }, ref) => {
React.useImperativeHandle(ref, () => ({}));
return (
<div
style={{
background: color,
width: 250,
height: 250
}}
/>
);
});
🤷♂️
edit:
i apologize, clearly didn't get why it worked. If I test the ref value it does get set to an empty object so React doesn't seem to be doing anything magic here.
I think we should investigate this further, really weird behaviour
This is because of:
https://github.com/react-spring/react-spring/blob/fe43bb7193512233c9954bda87ff651a9a1ab3cf/src/animated/createAnimatedComponent.tsx#L91-L94
It needs the reference because it has to write values to it, though for a function component it could probably just detect that it's one and skip this step, it would call forceUpdate anyway to update it, since it doesn't have a dom representation. But that's the weird part, with forwardRef fn-comps could actually be made so that they return their inner div as ref - in that case "native" animation would work without forceUpdate.
Basically ... is it possible to detect a function component that does not forward refs?
It should be yes!
Here is the implementation from is-react:
https://github.com/treyhuffine/is-react/blob/master/index.js#L12
that's hella dirty for a check that can run 60fps, though. 😕 it would be better (more performant) in that case to ignore the warning, it's harmless anyway and will lead to the same result -> the animation will be forceUpdated through react. a clean solution would be nice.
Edit: not a good fix.
I solved this just by wrapping my "stateless functional components" which were coming from a library inside an HOC... I don't know if it's a proper fix
function makeClassComponent(WrappedComponent) {
return class extends React.Component {
render() {
return <WrappedComponent {...this.props} />;
}
};
}
// make my SVG animated
const AnimatedCircle = animated(makeClassComponent(Circle));
While writing my original answer mentioning is-react I did not realise that the React team published their own library react-is which has a much better implementation, though it doesn't have a way to tell functional components from class based ones, but it's easy to tell that something is a class based component component.prototype.isReactComponent so any other function ought to be a functional component?
Not sure if this solution is satisfactory or performant enough though, so pardon me!
@Mathspy That looks quite good, didn't know about it thanks for digging it up! I will take another look after fixing some issues that were piling up here.
@drcmda Good luck and thank you for the amazing work! ❤️
@mcernusca's version is working but I think there are other ways to workaround the forwardRef warning.
Please have a look at the following Codesandbox.
As mentioned, it will also work with-out myRef but I've used that version in the example.
The useImperativeHandle Hook is a bit diffcult to understand for me - I'm pretty new to hooks. But as mentioned in the docs it should be avoided. As I understand, it will make the reference available for React-Spring but the warning is still present - maybe because of the empty object.
With-out useImperativeHandle the animation is not working.
I read that forwardRef will be made obsolete soon in React, that would be one worry less. :-)
I just ran into the same issue, and after more than a week of re-writing code probably 100 times, I ended up also using useImperativeHandle(), and that led me to this issue (that I wish I had found a week ago). I was forwarding refs on purpose, so I didn't run into the error you did, but my animation would not fire unless immediate was set to true, or I was actively re-rendering the component (in my case with a mouse drag).
Sorry for letting you wait so long. I hope it's fixed now. Refs in React are still weird. But i do a simple check now and if it's a plain function, it doesn't try to set a reference on it. I hope we can remove this once React throws out forwardRef all together.
Most helpful comment
That used to be a limitation in React before hooks but you still need to opt in essentially. See: https://reactjs.org/docs/hooks-reference.html#useimperativehandle
This seems to work: