Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How target DOM with react useRef in map

I looking for a solution about get an array of DOM elements with react useRef() hook.

example:

const Component = () =>  {    // In `items`, I would like to get an array of DOM element   let items = useRef(null);    return <ul>     {['left', 'right'].map((el, i) =>       <li key={i} ref={items} children={el} />     )}   </ul> } 

How can I achieve this?

like image 357
BrownBe Avatar asked Mar 01 '19 08:03

BrownBe


People also ask

How do you target DOM in React?

Instead of manually trying to add a class to a DOM node, you can keep a variable in your state indicating if the sidebar is open and change the value of that when the button is clicked. You can then use this state variable to decide if the sidebar should be given the hidden class or not.

How do you use useRef in React for input?

To make it work you'll need to create a reference to the input, assign the reference to ref attribute of the tag, and after mounting call the special method element. focus() on the element. Try the demo. const inputRef = useRef() creates a reference to hold the input element.

How does useRef work in React?

useRef returns a mutable ref object whose .current property is initialized to the passed argument ( initialValue ). The returned object will persist for the full lifetime of the component. Essentially, useRef is like a “box” that can hold a mutable value in its .current property.

How to use React useref?

How to use React useRef? Once created, you can get and set the value of the ref by accessing the .current property of the object, like so: To access a DOM element, you create a ref, assign it to the DOM element you want to target using its ref attribute, then you can use it!

How do I use createref with react usememo?

Because useMemo accepts a callback as an argument instead of a value, React.createRef will only be initialized once, after the first render. Inside the callback you can return an array of createRef values and use the array appropriately. const refs= useMemo ( () => Array.from ( { length: 3 }).map ( () => createRef ()), [] );

How do I store a reference to a DOM node in react?

The function receives the React component instance or HTML DOM element as its argument, which can be stored and accessed elsewhere. The example below implements a common pattern: using the ref callback to store a reference to a DOM node in an instance property.

How to set inputref to current in react?

Try the demo. const inputRef = useRef () creates a reference to hold the input element. inputRef is then assigned to ref attribute of the input field: <input ref= {inputRef} type="text" />. React then, after mounting, sets inputRef.current to be the input element.


2 Answers

useRef is just partially similar to React's ref(just structure of object with only field of current).

useRef hook is aiming on storing some data between renders and changing that data does not trigger re-rendering(unlike useState does).

Also just gentle reminder: better avoid initialize hooks in loops or if. It's first rule of hooks.

Having this in mind we:

  1. create array and keep it between renders by useRef

  2. we initialize each array's element by createRef()

  3. we can refer to list by using .current notation

    const Component = () => {    let refs = useRef([React.createRef(), React.createRef()]);    useEffect(() => {     refs.current[0].current.focus()   }, []);    return (<ul>     {['left', 'right'].map((el, i) =>       <li key={i}><input ref={refs.current[i]} value={el} /></li>     )}   </ul>) } 

This way we can safely modify array(say by changing it's length). But don't forget that mutating data stored by useRef does not trigger re-render. So to make changing length to re-render we need to involve useState.

const Component = () => {    const [length, setLength] = useState(2);   const refs = useRef([React.createRef(), React.createRef()]);    function updateLength({ target: { value }}) {     setLength(value);     refs.current = refs.current.splice(0, value);     for(let i = 0; i< value; i++) {       refs.current[i] = refs.current[i] || React.createRef();     }     refs.current = refs.current.map((item) => item || React.createRef());   }    useEffect(() => {    refs.current[refs.current.length - 1].current.focus()   }, [length]);    return (<>     <ul>     {refs.current.map((el, i) =>       <li key={i}><input ref={refs.current[i]} value={i} /></li>     )}   </ul>   <input value={refs.current.length} type="number" onChange={updateLength} />   </>) } 

Also don't try to access refs.current[0].current at first rendering - it will raise an error.

Say

      return (<ul>         {['left', 'right'].map((el, i) =>           <li key={i}>             <input ref={refs.current[i]} value={el} />             {refs.current[i].current.value}</li> // cannot read property `value` of undefined         )}       </ul>) 

So you either guard it as

      return (<ul>         {['left', 'right'].map((el, i) =>           <li key={i}>             <input ref={refs.current[i]} value={el} />             {refs.current[i].current && refs.current[i].current.value}</li> // cannot read property `value` of undefined         )}       </ul>) 

or access it in useEffect hook. Reason: refs are bound after element is rendered so during rendering is running for the first time it is not initialized yet.

like image 53
skyboyer Avatar answered Sep 20 '22 15:09

skyboyer


I'll expand on skyboyer's answer a bit. For performance optimization (and to avoid potential weird bugs), you might prefer to use useMemo instead of useRef. Because useMemo accepts a callback as an argument instead of a value, React.createRef will only be initialized once, after the first render. Inside the callback you can return an array of createRef values and use the array appropriately.

Initialization:

  const refs= useMemo(     () => Array.from({ length: 3 }).map(() => createRef()),     []   ); 

Empty array here (as a second argument) tells React to only initialize refs once. If ref count changes you may need to pass [x.length] as "a deps array" and create refs dynamically: Array.from({ length: x.length }).map(() => createRef()),

Usage:

  refs[i+1 % 3].current.focus(); 
like image 22
beqa Avatar answered Sep 22 '22 15:09

beqa