Conditionally assign ref in react
Asked Answered
S

4

9

I'm working on something in react and have encountered a challenge I'm not being able to solve myself. I've searched here and others places and I found topics with similar titles but didn't have anything to do with the problem I'm having, so here we go:

So I have an array which will be mapped into React, components, normally like so:

export default ParentComponent = () => {

//bunch of stuff here and there is an array called arr

return (<>
 
    {arr.map((item, id) => {<ChildComponent props={item} key={id}>})}

</>)

}

but the thing is, there's a state in the parent element which stores the id of one of the ChildComponents that is currently selected (I'm doing this by setting up a context and setting this state inside the ChildComponent), and then the problem is that I have to reference a node inside of the ChildComponent which is currently selected. I can forward a ref no problem, but I also want to assign the ref only on the currently selected ChildComponent, I would like to do this:

export default ParentComponent = () => {

//bunch of stuff here and there is an array called arr and there's a state which holds the id of a  selected ChildComponent called selectedObjectId

const selectedRef = createRef();

return (<>
    <someContextProvider>
    {arr.map((item, id) => {
       <ChildComponent 
        props={item} 
        key={id} 
        ref={selectedObjectId == id ? selectedRef : null}
       >
    })}
   <someContextProvider />
</>)

}

But I have tried and we can't do that. So how can dynamically assign the ref to only one particular element of an array if a certain condition is true?

Shrum answered 26/5, 2021 at 5:5 Comment(3)
Using a React ref to store what amounts to an active id seems a poor use of a React ref, especially considering you are already using a React context... why not just access the context in each child and match the active id to that of the child component?Nephology
actually I'm using the SelectiveBloom effect from @react-three/fiber and postprocessing, and in order to achieve this the SelectiveBloom needs a ref for the mesh its going to aplly the bloom. So I need that for each ChildComponent, if the selected component id is the id of this ChildComponent, I want to pass a ref of a mesh inside it to the SelectiveBloom effect in the parent component. In order words, I want to aplly a SelectiveBloom only in the selected object.Shrum
I think you can do ref={ function(el) { if(el && selectedObjectId === id) { selectedRef.current = el; } } }.Hermy
S
-1

Solution

Like Drew commented in Medets answer, the only solution is to create an array of refs and access the desired one by simply matching the index of the ChildElement with the index of the ref array, as we can see here. There's no way we found to actually move a ref between objects, but performance cost for doing this should not be relevant.

Shrum answered 26/5, 2021 at 23:0 Comment(0)
K
18

You can use the props spread operator {...props} to pass a conditional ref by building the props object first. E.g.

export default ParentComponent = () => {
  const selectedRef = useRef(null);

  return (
    <SomeContextProvider>
      {arr.map((item, id) => {
        const itemProps = selectedObjectId == id ? { ref: selectedRef } : {};
        return ( 
          <ChildComponent 
            props={item} 
            key={id} 
            {...itemProps}
          />
        );
      })}
    <SomeContextProvider />
  )
}
Kellner answered 11/2, 2022 at 6:25 Comment(1)
I get error: Function components cannot be given refs.Decor
H
1

You cannot dynamically assign ref, but you can store all of them, and access by id

export default ParentComponent = () => {

//bunch of stuff here and there is an array called arr and theres a state wich holds the id of a  selected ChildComponent called selectedObjectId


let refs = {}

// example of accessing current selected ref
const handleClick = () => {
    if (refs[selectedObjectId])
        refs[selectedObjectId].current.click() // call some method
}

return (<>
    <someContextProvider>
    {arr.map((item, id) => {
       <ChildComponent 
        props={item} 
        key={id} 
        ref={refs[id]}
       >
    })}
   <someContextProvider />
</>)

}
Haematoxylin answered 26/5, 2021 at 5:29 Comment(5)
Thanks, but isn't it very bad for performance? React docs speciffically says to not abuse refs, and I will just not be using any of them expect one.Shrum
Medet and Daniel: See this answer for generating the React refs. Note that the parent component also uses a single ref to hold an array of refs for the children. In fact Medet, your question here is nearly a duplicate of the other one I've answered, i.e. it has the same issue/pitfall.Nephology
Hi Drew, I had already seen that solution, but I thought that creating an array of refs even though I would only use one (that would migrate between objects) was very very bad por performance. Am I wrong? Well, I guess that's the only solution anyway. Thank you!Shrum
@DanielGuedes Refs should be created once per component, and persist through rerenders... I would say there's more of a hit to declare an extra variable than there'd be in any performance cost. In other words, I don't think you'd even notice anything other than possibly more memory usage.Nephology
Thank yoy very much, still learning a lot, glad there's people like you!Shrum
O
0

In fact, dynamically specifying a ref is feasible. React provides the MutableRefObject type for this purpose.

//...
const selectedRef = useRef<HTMLButtonElement | null>(null)
const handleClick = (e: React.MouseEvent) => {
    selectedRef.current = e.currentTarget as HTMLButtonElement
}
//...

However, in practical production scenarios, RefCallback might be more useful.

Othelia answered 9/5 at 1:26 Comment(0)
S
-1

Solution

Like Drew commented in Medets answer, the only solution is to create an array of refs and access the desired one by simply matching the index of the ChildElement with the index of the ref array, as we can see here. There's no way we found to actually move a ref between objects, but performance cost for doing this should not be relevant.

Shrum answered 26/5, 2021 at 23:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.