Clarify React Refs by Examples
Adam C. |

I barely used refs in my about 5 years with ReactJS. I knew that it has been used to do some native Javascript actions, like ‘focus’, ‘scrolling’, ‘text selection’, etc. It's a bit like jQuery way to manipulate the DOM. Isn't it? 

Photo By: Nathan Shively

But one day, I saw the error shown on my console of my React App, like this:

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

Then I started learning it. Before we get into this issue, let's learn some basic concepts first. Note that in this article, we use new React API, like createRef(), useRef(), forwardRef(), etc. so if you are still using old API, i.e. string refs, then you may upgrade it, because,

We advise against it because string refs have some issues, are considered legacy, and are likely to be removed in one of the future releases.

See All Examples used in this article at Codepen: https://codepen.io/collection/AOaQkG

Example 1 - Ref Focus in Class Component

class Ref extends React.Component {
  constructor(props) {
    super(props);
    this.firstNameRef = React.createRef();
    this.lastNameRef = React.createRef();
  }

  componentDidMount() {
    this.firstNameRef.current.focus();
  }

  handleKeyDown = (e) => {
    if (e.key === "Enter") {
      this.lastNameRef.current.focus();
    }
  };

  render() {
    return (
      <Layout>
        <Form>
          <Form.Field>
            <label>First Name</label>
            <input
              placeholder="First Name"
              ref={this.firstNameRef}
              onKeyDown={this.handleKeyDown}
            />
          </Form.Field>
          <Form.Field>
            <label>Last Name</label>
            <input placeholder="Last Name" ref={this.lastNameRef} />
          </Form.Field>
        </Form>
      </Layout>
    );
  }
}

We create Refs in Constructor and assign it to the input fields, then we can access the DOM element by using, for example, this.firstNameRef.current, then focus it. 

Demo1: https://deniapps.com/playground/ref/class-ref-focus

Example 2 - Ref Focus in Functional Component

const UseRef = () => {
  const firstNameRef = useRef();
  const lastNameRef = useRef();

  useEffect(() => {
    firstNameRef.current.focus();
    // document.getElementById("dnx-input").focus();
  }, []);

  const handleKeyDown = (e) => {
    if (e.key === "Enter") {
      lastNameRef.current.focus();
    }
  };

  return (
    <Layout>
      <Form>
        <Form.Field>
          <label>First Name</label>
          <input
            placeholder="First Name"
            ref={firstNameRef}
            onKeyDown={handleKeyDown}
          />
        </Form.Field>
        <Form.Field>
          <label>Last Name</label>
          <input placeholder="Last Name" ref={lastNameRef} />
        </Form.Field>
      </Form>
    </Layout>
  );

Since it's a functional component, we use useRef() instead of createRef(), other than that, both components are pretty much identical.

Demo2: https://deniapps.com/playground/ref/functional-ref-focus

Example 3 - Use Ref to Change Value - (DON'T DO THIS)

class Ref extends React.Component {
  constructor(props) {
    super(props);
    this.yourRef = React.createRef();
  }

  componentDidMount() {
    this.yourRef.current.focus();
  }

  incremenetInput = () => {
    this.yourRef.current.value++;
  };

  render() {
    return (
      <Layout>
        <Form>
          <Form.Field>
            <label>Just a number</label>
            <input
              placeholder="Enter Value"
              ref={this.yourRef}
              onKeyDown={this.handleKeyDown}
            />
          </Form.Field>
          <Button onClick={this.incremenetInput}>Increase Value</Button>
        </Form>
      </Layout>
    );
  }
}

So, yes, we could change the value of the DOM element using Ref - back to the age of jQuery! But, we should avoid using refs for anything that can be done declaratively.

Demo3: https://deniapps.com/playground/ref/class-ref-change-value

Example 4 - Forward Ref Using React.forwardRef

//Child Component
const ChildInput = React.forwardRef((props, ref) => {
   return <input {...props} ref={ref} />;
});

//Parent Component
const FunctionalForwardRef = () => {
  const childRef = useRef();

  const handleClick = () => {
    childRef.current.focus();
  };

  return (
    <Layout>
      <Header as="h3">Ref Forward Demo</Header>
      <ChildInput type="text" ref={childRef} />
      <Button onClick={handleClick}>Focus Child Input</Button>
    </Layout>
  );
};

Forwarding Ref means forward the ref from the parent component to the child component, so we can control the DOM element in the child component from the parent component.  But the ‘ref’ is a reserved word in React JSX,  we cannot use it as a prop, which means we cannot pass thru ref using props.ref, so we should call React.forwardRef to pass thru ref. - Again, props, key, ref are three reserved words. 

Demo4: https://deniapps.com/playground/ref/forward-ref

Example 5 - Forward Ref Using Custom Prop

//Child Component
const ChildInput = (props) => {
   return <input ref={props.forwardedRef} />;
});

//Parent Component
const FunctionalForwardRef = () => {
  const childRef = useRef();

  const handleClick = () => {
    childRef.current.focus();
  };

  return (
    <Layout>
      <Header as="h3">Ref Forward Demo</Header>
      <ChildInput type="text" forwardedRef={childRef} />
      <Button onClick={handleClick}>Focus Child Input</Button>
    </Layout>
  );
};

Actually, we could use Custom Prop to save Ref like other props, forwardedRef={childRef}, Noted that forwardedRef is a custom name, and you can name it everything meaningful as long as it's not a reserved word. 

Cool, this is much simpler, why bother to use React.forwardRef()? One benefit is that by using React.forwardRef(), we separate the props and ref, then we could pass the props, using {…props}.

Demo5: https://deniapps.com/playground/ref/functional-forward-ref

Forward Ref Warning

Now let's back the issue we see in the browser console as mentioned at the beginning of the article. See the example below:

//Child Component - without passing thru the ref causes the warning
const ChildInput = (props) => {
   return <input {...props} />;
});

//Parent Component
const FunctionalForwardRef = () => {
  const childRef = useRef();

  const handleClick = () => {
    childRef.current.focus();
  };

  return (
    <Layout>
      <Header as="h3">Ref Forward Demo</Header>
      <ChildInput type="text" ref={childRef} />
      <Button onClick={handleClick}>Focus Child Input</Button>
    </Layout>
  );
};

Since we did not pass thru the ref in this example, the React throws the warnings:

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

Last Thing

As we learned in Example 3, we could use Ref to change the DOM element's value, although we don't recommend to do this, if we really really want to do it, then we should be aware that we cannot use Ref to change the value of  the controlled component, for example, if we use semantic-ui-react to render input, like this:

import { Input, Button } from "semantic-ui-react";
incremenetInput = () => {
   this.yourRef.current.value++;
};
....
<Input type="text" ref={this.yourRef} defaultValue="0" />
<Button onClick={this.incremenetInput}>Increment Input Value</Button>

We would not be able to update the value of <Input>. That's because the Input is an object, like this:

Input {props: {…}, context: {…}, refs: {…}, updater: {…}, inputRef: {…}, …}
computeIcon: ƒ ()
computeTabIndex: ƒ ()
context: {}
focus: ƒ ()
handleChange: ƒ (e)
handleChildOverrides: ƒ (child, defaultProps)
inputRef: {current: input}
partitionProps: ƒ ()
props: {type: "text", defaultValue: "0"}
refs: {}
select: ƒ ()
state: null
updater: {isMounted: ƒ, enqueueSetState: ƒ, enqueueReplaceState: ƒ, enqueueForceUpdate: ƒ}
value: "10"

It's a controlled component, the value of the input is in props not in ref.current.

See All Examples used in this article at Codepen: https://codepen.io/collection/AOaQkG

That's it - happy Refing :-)

Bonus Example (3/26/2021)

I saw a question at S0 asking how to pass ‘Ref’ to {Children}. This is pretty tricky, because {Children} is a Prop, to pass Ref, we basically need to pass Prop to {Children}.  After some research, I figured out the solution by using React's Context.

const MyContext = React.createContext();

function Widget(props) {
  const myRef = useRef(null);
  useEffect(() => {
    myRef.current.focus();
  });
  return (
    <MyContext.Provider value={myRef}>{props.children}</MyContext.Provider>
  );
}

function Example() {
  return (
    <Widget>
      <MyContext.Consumer>
        {(forwardedRef) => <input type="text" ref={forwardedRef} />}
      </MyContext.Consumer>
    </Widget>
  );
}

Check out the demo at CodeSandbox