This article introduces useRef in all aspects, including what useRef is, why to use useRef, and the three brothers of useRef (useRef, forwardRef, useImperativeHandle).

All examples in this article

What is useRef

const refContainer = useRef(initialValue);
Copy the code
  • Returns a variable ref object with only a current property and the initialValue of the passed parameter (initialValue).
  • The returned REF object remains the same throughout the lifetime of the component
  • It does not re-render when updating the current value, unlike useState
  • Updating useRef is a side effect, so it is usually written in the useEffect or Event Handler
  • UseRef is similar to this for the class component

A simple example

Requirement: Select the text box when clicking button

Implementation:

import React, { MutableRefObject, useRef } from 'react'
const TextInputWithFocusButton: React.FC = () = > {
   const inputEl: MutableRefObject<any> = useRef(null)
   const handleFocus = () = > {
       // 'current' refers to the text input element that has been mounted to the DOM
       inputEl.current.focus()
   }
   return (
       <p>
           <input ref={inputEl} type="text" />
           <button onClick={handleFocus}>Focus the input</button>
       </p>)}export default TextInputWithFocusButton
Copy the code

Summary:In this way, you can get the INPUT Dom element through inputel. current. If selected, you can call the focus function

See domuseref.tsx in the sample library

Why useRef

Requirement: Get status values across the render

Only useState:

Implementation:

import React, { useState } from "react";
const LikeButton: React.FC = () = > {
    const [like, setLike] = useState(0)
    function handleAlertClick() {
        setTimeout(() = > {
            alert(`you clicked on ${like}`) 
            // Form a closure, so it pops up with the like value when the function was fired
        }, 3000)}return (
        <>
            <button onClick={()= >SetLike (like + 1)} > {like} to praise</button>
            <button onClick={handleAlertClick}>Alert</button>
        </>)}export default LikeButton
Copy the code

The phenomenon of: If like is 6, click alert and increase like to 10. The value pops up to 6 instead of 10.Why not live status of like on the interface?When we change the state, React rerenders the component, each time it gets a separate like value, and redefines a handleAlertClick function. Each handleAlertClick function body has its own like value, so when like is 6, Clicking On Alert triggers a handleAlertClick, where the like is 6, and even if you change the like to 10, the like for alert is already set.

Summary: The state value cannot be shared between different renderings

See likeButton in the sample library

Adopt global variables

Define a variable like global in front of the component

Implementation:

import React from "react";
let like = 0;
const LikeButton: React.FC = () = > {
  function handleAlertClick() {
    setTimeout(() = > {
      alert(`you clicked on ${like}`);
    }, 3000);
  }
  return (
    <>
      <button
        onClick={()= >{ like = ++like; }} > {like} to assist</button>
      <button onClick={handleAlertClick}>Alert</button>
    </>
  );
};
export default LikeButton;
Copy the code

The phenomenon ofWhen like is 6, click Alert and continue to increase like to 10. The popup value is 10.summarySince the like variable is defined outside the component, it can be shared between different renderings, so the like value obtained after 3 seconds is the latest like valueThe example also shows that non-state variables do not cause rerender

See globalFix1 in the sample library for the code

Using useRef

Implementation:

import React, { useRef } from "react";
const LikeButton: React.FC = () = > {
  // Define an instance variable
  let like = useRef(0);
  function handleAlertClick() {
    setTimeout(() = > {
      alert(`you clicked on ${like.current}`);
    }, 3000);
  }
  return (
    <>
      <button
        onClick={()= >{ like.current = like.current + 1; }} > {like. Current}</button>
      <button onClick={handleAlertClick}>Alert</button>
    </>
  );
};
export default LikeButton;
Copy the code

The phenomenon ofWhen like is 6, click Alert and continue to increase like to 10. The popup value is 10. This is the same as using global variables abovesummaryUse useRef, as a variable for the component instance, to ensure that the data is up to date.

The example also shows that ref changes do not re-render

See useRefFix2 in the sample library

The difference between useRef and global variables

What’s the difference between the two approaches

implementation

import React, { useRef } from "react";
// Define a global variable
let like = 0;
const LikeButton: React.FC = () = > {
  let likeRef = useRef(0);
  function handleAlertClick() {
    setTimeout(() = > {
      alert(`you clicked on ${like}`);
      alert(`you clicked on ${likeRef.current}`);
    }, 3000);
  }
  return (
    <p>
      <button
        onClick={()= >{ like = ++like; likeRef.current = likeRef.current + 1; }} > thumb up</button>
      <button onClick={handleAlertClick}>Alert</button>
    </p>
  );
};
export default LikeButton;
Copy the code

The phenomenon ofClick the three buttons in turn, click any alert, the first pop-up is 3(indicating the value of the last rendered component taken by the global variable), and the second pop-up is 1(indicating that the ref belongs to the component itself and does not affect each other).

summary

  • UseRef is defined on an instance basis. If you have multiple identical components in your code, the ref of each component is related only to the component itself, not to the ref of other components.
  • The global variable defined before the component is global. If there are multiple components in the code that are the same, then the global variable is the same globally and they affect each other.

See the differenceFix1and2.tsx in the sample library for the code

The difference between useRef and createRef

The normal life cycle of a component can be roughly divided into three phases:

  1. From creating the component to mounting to the DOM phase. Initialize props and state, and build the DOM based on state and props
  2. The props and state of the component dependency changed, triggering the update. Procedure
  3. Destruction of phase

In the first stage, useRef is no different from createRef

In the second phase, createRef returns a new reference each time; UseRef is not recreated with component updates

In the third stage, both will be destroyed

Implementation:

import React, { useState, useRef, createRef } from 'react'
const RefDifference: React.FC = () = > {
    let [renderIndex, setRenderIndex] = useState(1)
    let refFromUseRef = useRef<number>()
    let refFromCreateRef = createRef()
    console.info(refFromUseRef.current, 'refFromUseRef.current')
    console.info(refFromCreateRef.current, 'refFromCreateRef.current')
    if(! refFromUseRef.current) { refFromUseRef.current = renderIndex }if(! refFromCreateRef.current) { refFromCreateRef.current = renderIndex }return (
        <>
            <p>Current render index: {renderIndex}</p>
            <p>
                <b>refFromUseRef</b> value: {refFromUseRef.current}
            </p>
            <p>
                <b>refFromCreateRef</b> value:
                {refFromCreateRef.current}
            </p>
            <button onClick={()= > setRenderIndex((prev) => prev + 1)}>
                Cause re-render
            </button>
        </>)}export default RefDifference
Copy the code

Phenomenon:When you click the button, you can see from the console that reffromuseref.current is always 1(because reffromuseref.current already has this reference), Reffromcreateref. current is undefined(because createRef returns a new reference every time it is rendered, so if is true and will be reassigned and the page will display the new value).Summary:CreateRef returns a new reference each time it is rendered, while useRef returns the same reference each time

See useRefAndCreateRef in the sample library for the code

These are all examples of using ref in the current component. How can ref be used in interactions with child components

Get the properties or methods of the child component

Requirement: Call a function in a child component

Custom properties are passed into the REF

Implementation: The parent component creates a REF that is passed to the child component as a property. Children dynamically change ref (useEffect) based on internal method changes

import React, {
    MutableRefObject,
    useState,
    useEffect,
    useRef,
    useCallback
} from 'react'
interface IProps {
    //prettier-ignore
    label: string,
    cRef: MutableRefObject<any>
}
const ChildInput: React.FC<IProps> = (props) = > {
    const { label, cRef } = props
    const [value, setValue] = useState(' ')
    const handleChange = (e: any) = > {
        const value = e.target.value
        setValue(value)
    }
    const getValue = useCallback(() = > {
        return value
    }, [value])
    useEffect(() = > {
        if (cRef && cRef.current) {
            cRef.current.getValue = getValue
        }
    }, [getValue])
    return (
        <div>
            <span>{label}:</span>
            <input type="text" value={value} onChange={handleChange} />
        </div>)}const ParentCom: React.FC = (props: any) = > {
    const childRef: MutableRefObject<any> = useRef({})
    const handleFocus = () = > {
        const node = childRef.current
        alert(node.getValue())
    }
    return (
        <div>
            <ChildInput label={'name'}cRef={childRef} />
            <button onClick={handleFocus}>focus</button>
        </div>)}export default ParentCom

Copy the code

Phenomenon:When the parent component button is clicked, getValue is called to retrieve the value in the child component inputSummary:Not elegant enough, especially to customize a property passed to ref

See childComponentref.tsx in the sample library

Through useImperativeHandle, cooperate with forwardRef

ForwardRef: Pass the parent ref as a parameter to the function component

Example:

React.forwardRef((props, ref) = > {})  
// Create a React component,
// This component will accept the ref attribute passed by the parent,
// We can attach a parent ref to a dom element of the child component.
// The parent component gets the DOM element through the ref
Copy the code
const FancyButton = React.forwardRef((props, ref) = > (  
  <button ref={ref} className="FancyButton">    
    {props.children}
  </button>
));
// You can directly retrieve the DOM node of a button
const ref = React.useRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
Copy the code

**useImperativeHandle:** In functional components, the ref method that defines exposure to the parent component limits the information that the child component exposes. Only the properties and methods defined in the second parameter of useImperativeHandle are available to the parent component

Why: Because using forward+useRef to get a subfunction DOM exposes too many DOM attributes

Solution: Use uesImperativeHandle to define the DOM operations that the parent component needs to perform in the child function component. Any operations that are not defined are not exposed to the parent component

useImperativeHandle(ref, createHandle, [deps]) // The first parameter exposes which ref; What information does the second parameter expose
Copy the code
function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () = > ({
    focus: () = >{ inputRef.current.focus(); }}));return <input ref={inputRef} . />;
}
FancyInput = forwardRef(FancyInput);
// Render the parent component of 
      
// You can call inputref.current.focus ()
Copy the code

Implementation:

import React, {
    MutableRefObject,
    useState,
    useImperativeHandle,
    useRef,
    forwardRef,
    useCallback
} from 'react'
interface IProps {
    label: string
}
let ChildInput = forwardRef((props: IProps, ref: any) = > {
    const { label } = props
    const [value, setValue] = useState(' ')
    // Function: reduce the number of DOM element attributes that the parent component obtains, exposing only the DOM methods that the parent component needs
    // Parameter 1: the ref attribute passed by the parent
    // Argument 2: returns an object whose methods are called by the parent through ref.current
    useImperativeHandle(ref, () = > ({
        getValue
    }))
    const handleChange = (e: any) = > {
        const value = e.target.value
        setValue(value)
    }
    const getValue = useCallback(() = > {
        return value
    }, [value])
    return (
        <div>
            <span>{label}:</span>
            <input type="text" value={value} onChange={handleChange} />
        </div>)})const ParentCom: React.FC = (props: any) = > {
    const childRef: MutableRefObject<any> = useRef({})
    const handleFocus = () = > {
        const node = childRef.current
        alert(node.getValue())
    }
    return (
        <div>
            <ChildInput label={'name'}ref={childRef} />
            <button onClick={handleFocus}>focus</button>
        </div>)}export default ParentCom

Copy the code

Phenomenon:

Summary:Before React 16.3, it was not possible to define the ref attribute in function components because function components have no instances. After version 16.3, the react.forwardref function was introduced, which passes a parent ref into a child component. The child component can bind the ref to any DOM element, but this exposes the entire child component to the parent. UseImperativeHandle allows you to restrict which properties or methods in the child component are exposed to the parent component

Note: After wrapping the child component with ANTD’s form.create (), WrappedComponentRef ={childRef} /> wrappedComponentRef={childRef} />

See childComponentRef2 in the sample library

Five, the summary

  1. UseRef can be used to define variables that will not cause the page to be rerendered if changed, such as storing page numbers when paging for data.
  2. UseRef can also be used to distinguish between initial rendering and update (see didorupdate.tsx in the sample library).
  3. The DOM element is obtained by defining the ref attribute on the DOM node through.current
  4. The ref property is passed through the forwardRef.
  5. Use useImperativeHandle to define the ref method exposed to the parent component