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?
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.
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.
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? 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!
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 ()), [] );
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.
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.
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:
create array and keep it between renders by useRef
we initialize each array's element by createRef()
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: ref
s are bound after element is rendered so during rendering is running for the first time it is not initialized yet.
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();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With