Search code examples
reactjsfont-awesomereact-hooksstyled-components

React Hooks - "Function components cannot be given refs" with useRef


I'm using styled-components, FontAwesome and React Hooks to create an icon & label unit (Box.js) for a header/navbar. Inside the Box there is a Container which holds both the Icon and Label. I want both the Icon and Label to change colors together when you hover over either of them which is why I put onMouseOver and onMouseOut on the Container component. I was loosely following this guide to implement something like that.

Before following the link, I tried this useHover hook which did not give me an error message but produced no effect (not even console.log output when I hovered over).

I've googled the error message, but it looks like existing StackOverflow posts and React issues on Github are not dealing with Hooks. The error messages reported in those are also slightly different: Warning: Stateless function components cannot be given refs. Attempts to access this ref will fail. My error message does not have the word "stateless."

Working example here: https://codesandbox.io/s/q7rwe

Expected: all the child elements of Container (i.e. Icon and Label) change to the color green when the mouse hovers over any one child element.

Actual: I get the following error message -

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Check the render method of `Box`.
    in Icon (created by Box)
    in div (created by Context.Consumer)
    in StyledComponent (created by styled.div)
    in styled.div (created by Box)
    in Box (created by App)
    in div (created by App)
    in App

Solution

  • There are a few flaws in your current approach.

    First of all, you should not give a ref to a functional component.

    But, even if you do, you won't be able to use the ref to change color given your current structure, because useEffect will be activated after rendering and immediately make the component's color green because ref.current will exist.

    You have isHovered state already. Just use this for changing color.

    I have updated your code to better implement the behavior you want. Try it!

    Box.js

    const Box = () => {
      const [isHovered, setHovered] = useState(false);
    
      return (
        <Container
          onMouseOver={() => setHovered(true)}
          onMouseOut={() => setHovered(false)}
        >
          <Icon isHovered={isHovered} />
          <Label isHovered={isHovered} />
        </Container>
      );
    };
    

    Icon.js

    import React from "react";
    import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
    import { faCoffee } from "@fortawesome/free-solid-svg-icons";
    const Icon = props => {
      return (
        <FontAwesomeIcon
          icon={faCoffee}
          color={props.isHovered ? "green" : "black"}
        />
      );
    };
    
    export default Icon;
    
    

    Label.js

    import React from "react";
    const Label = props => {
      return (
        <p style={{ color: props.isHovered ? "green" : "black" }}>
          Label goes here
        </p>
      );
    };
    export default Label;
    
    

    https://codesandbox.io/embed/useref-error-xjf48