React functional components use ref

React functional components use Ref

React functional components use ref

In React, ref can help us get instances of subcomponents or DOM objects

UseRef

 const inputRef = useRef(init);

useRef returns a mutable ref object whose .current property is initialized to the passed parameter (init). The returned ref object remains unchanged throughout its lifetime.
Let’s see an example:

import React, {<!-- --> useState, useCallback, useEffect, useRef, } from 'react';
import {<!-- --> Input, Button } from 'antd';
import styles from './index.less';

const Index = (props) => {<!-- -->
  const inputEle = useRef(null)

  const onBurronClick = () => {<!-- -->
    inputEle. current. focus();
  };
  return (
    <div className={<!-- -->styles.warp_content}>
      <div>
        <Input ref={<!-- -->inputEle } style={<!-- -->{<!-- --> width: 300 }}></Input>
      </div>
      <div>
        <Button onClick={<!-- -->onBurronClick}>Focus</Button>
      </div>
    </div>
  );
};

export default Index;

We click the Button, first create a ref object inputEle through useRef, then assign inputEle to the ref of Input, and finally, through inputEle.current.focus(), the Input can be focused. What if Input is not an ordinary dom element, but a component? This leads to another PAI, forwardRef

forwardRef

React.forwardRef takes a render function that takes props and ref parameters and returns a React node.

Let’s modify the above example and encapsulate Input into a component

import React, {<!-- -->useState, useRef, forwardRef } from 'react';
import {<!-- --> Input } from 'antd';

const InputText = forwardRef((props, ref) => {<!-- -->
  return <Input ref={<!-- -->ref} style={<!-- -->{<!-- --> width: 300 }}></Input>;
});
export default InputText;

Then refer to the InputText component in the Index component

import React, {<!-- --> useState, useCallback, useEffect, useRef, } from 'react';
import {<!-- --> Input, Button } from 'antd';
import InputText from './mod/inputText';
import styles from './index.less';

const Index = (props) => {<!-- -->
  const inputEle = useRef(null)

  const onBurronClick = () => {<!-- -->
    inputEle. current. focus();
  };
  return (
    <div className={<!-- -->styles.warp_content}>
      <div>
        <InputText ref={<!-- -->inputEle } style={<!-- -->{<!-- --> width: 300 }}></Input>
      </div>
      <div>
        <Button onClick={<!-- -->onBurronClick}>Focus</Button>
      </div>
    </div>
  );
};

export default Index;

React.forwardRef takes a render function that takes props and ref parameters and returns a React node.
In this way, we forward the ref created in the parent component into the child component, and assign it to the input element of the child component, and then call its focus method. Through the two APIs useRef and forwardRef, we can use ref in functional components. Using useRef and forwardRef will fully expose the child components to the parent element, but sometimes we don’t want to expose all the child components to the parent component, so what should we do, which requires us to use another API useImperativeHandle

useImperativeHandle

We can use useImperativeHandle to expose the properties or methods we need to expose to the parent component
grammar:

useImperativeHandle(ref, createHandle, [deps])

Let’s transform the InputText component above

import React, {<!-- -->useState, useRef, forwardRef, useImperativeHandle } from 'react';
import {<!-- --> Input } from 'antd';

const InputText = forwardRef((props, ref) => {<!-- -->
    const inputRef = useRef();
    useImperativeHandle(ref, () => ({<!-- -->
      focus: () => {<!-- -->
        inputRef. current. focus();
      }
    }));
  return <Input ref={<!-- -->inputRef} style={<!-- -->{<!-- --> width: 300 }}></Input>;
});

export default InputText;

Then reference it in the parent component

import React, {<!-- --> useState, useCallback, useEffect, useRef, } from 'react';
import {<!-- --> Input, Button } from 'antd';
import InputText from './mod/inputText';
import styles from './index.less';

const Index = (props) => {<!-- -->
  const inputEle = useRef(null)

  const onBurronClick = () => {<!-- -->
    inputEle. current. focus();
  };
  return (
    <div className={<!-- -->styles.warp_content}>
      <div>
        <InputText ref={<!-- -->inputEle } style={<!-- -->{<!-- --> width: 300 }}></Input>
      </div>
      <div>
        <Button onClick={<!-- -->onBurronClick}>Focus</Button>
      </div>
    </div>
  );
};

export default Index;

For the above code, we can also use current.focus() to focus the Input
In this way, we can only expose the focus method of the InputText component to the parent component. It should be noted that the useRef object in the subcomponent TextInput is only used to obtain the Input element, so don’t confuse it with the useRef in the parent component.
useRef, forwardRef, useImperativeHandle Use these three APIs to expose properties and methods that only need to be exposed to the parent component to the parent component.

Callback Ref

What is a callback Ref?
useRef will not notify you when the content of the ref object changes. Changing the .current property does not cause the component to re-render. So how to solve this problem, this requires the use of callback Ref.
Let’s look at a example first:
InputText component:

import React, {<!-- -->useState, useRef, forwardRef, useImperativeHandle } from 'react';
import {<!-- --> Input } from 'antd';

const InputText = forwardRef((props, ref) => {<!-- -->
  const [value, setValue] = useState('')
    const inputRef = useRef();
    useImperativeHandle(ref, () => ({<!-- -->
      value: inputRef.current.props.value,
    }));
    const changeValue = (e) =>{<!-- -->
      setValue(e. target. value);
    }
  return <Input ref={<!-- -->inputRef} value={<!-- -->value} onChange={<!-- -->changeValue} style={<!-- -->{ <!-- --> width: 300 }}/>;
});

export default InputText;

Index component:

import React, {<!-- --> useState, useCallback, useEffect, useRef, } from 'react';
import {<!-- --> Input, Button } from 'antd';
import styles from './index.less';
import InputText from './mod/inputText';

const Index = (props) => {<!-- -->
  const inputEle = useRef(null);
  const [value, setValue] = useState(null);
  useEffect(() => {<!-- -->
    setValue(inputEle. current. value);
  }, [inputEle]);
const onButtonClick = () => {<!-- -->
    // `current` points to the text input element mounted on the DOM
    console.log("input value", inputEl.current.value);
    setValue(inputEle. current. value);
  };
  
  return (
    <div className={<!-- -->styles.warp_content}>
      <div style={<!-- -->{<!-- --> paddingBottom: '15px' }}>
        <InputText ref={<!-- -->inputEle}></InputText>
      </div>
      <div>
        <Input onChange={<!-- -->() => {<!-- -->}} value={<!-- -->value} style={<!-- -->{< !-- --> width: 300 }}></Input>
      </div>
      <div>
        <Button onClick={<!-- -->onButtonClick }>Get value</Button>
      </div>
    </div>
  );
};

export default Index;

In the above example, when the Input in the file subgroup changes, the parent component cannot receive the value of the child component, and the value of the child component must be obtained by clicking the button. Even if the useEffect method is used to monitor inputEle, it will not help. Then what to do, you need to use the callback Ref.
Let’s modify the code above:

import React, {<!-- -->useState, useRef, forwardRef, useImperativeHandle } from 'react';
import {<!-- --> Input } from 'antd';

const InputText = forwardRef((props, ref) => {<!-- -->
    const [value, setValue] = useState('')
    const inputRef = useRef();
    useImperativeHandle(ref, () => ({<!-- -->
      value: inputRef.current.props.value,
    }));
    const changeValue = (e) =>{<!-- -->
      setValue(e. target. value);
    }
  return <Input ref={<!-- -->inputRef} value={<!-- -->value} onChange={<!-- -->changeValue} style={<!-- -->{ <!-- --> width: 300 }}></Input>;
});

export default InputText;

Index component:

import React, {<!-- --> useState, useCallback, useEffect, useRef, } from 'react';
import {<!-- --> Input, Button } from 'antd';
import styles from './index.less';
import InputText from './mod/inputText';

const Index = (props) => {<!-- -->
  // const inputEle = useRef(null);
  const [value, setValue] = useState(null);
  const inputEle = useCallback((node) => {<!-- -->
    if (node !== null) {<!-- -->
      console.log('Index -> node.value', node);
      setValue(node?. value);
    }
  }, []);
  return (
    <div className={<!-- -->styles.warp_content}>
      <div style={<!-- -->{<!-- --> paddingBottom: '15px' }}>
        <InputText ref={<!-- -->inputEle}></InputText>
      </div>
      <div>
        <Input onChange={<!-- -->() => {<!-- -->}} value={<!-- -->value} style={<!-- -->{< !-- --> width: 300 }}></Input>
      </div>
     
    </div>
  );
};

export default Index;

The above code can realize the value change of the child component, and the parent component can automatically obtain the value of the child component. The key code is to use useCallback instead of useRef, and callback will notify the parent component of the value change of the current ref.

Summary

1. ref can help us get instances or DOM objects of subcomponents
2. Using useRef() and forwardRef() will completely expose the child component to the parent element
3. useRef(), forwardRef(), useImperativeHandle() Use these three APIs to expose properties and methods that only need to be exposed to the parent component to the parent component.
4. Call back ref, use callback() instead of useRef(), callback() will notify the parent component of the value change of the current ref