5

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?

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Daniel Guedes
  • 387
  • 1
  • 4
  • 14
  • 2
    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? – Drew Reese May 26 '21 at 05:24
  • 1
    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. – Daniel Guedes May 26 '21 at 15:00
  • I think you can do `ref={ function(el) { if(el && selectedObjectId === id) { selectedRef.current = el; } } }`. – fweth Dec 02 '22 at 16:49

3 Answers3

10

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 />
  )
}
phdesign
  • 2,019
  • 21
  • 18
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 />
</>)

}
Medet Tleukabiluly
  • 11,662
  • 3
  • 34
  • 69
  • 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. – Daniel Guedes May 26 '21 at 13:58
  • 1
    Medet and Daniel: See [this answer](https://stackoverflow.com/questions/65350114/useref-for-element-in-loop-in-react/65350394#65350394) 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. – Drew Reese May 26 '21 at 16:18
  • 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! – Daniel Guedes May 26 '21 at 18:23
  • @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. – Drew Reese May 26 '21 at 21:15
  • Thank yoy very much, still learning a lot, glad there's people like you! – Daniel Guedes May 26 '21 at 22:54
-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.

Daniel Guedes
  • 387
  • 1
  • 4
  • 14