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?

3
  • 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? Commented May 26, 2021 at 5: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. Commented May 26, 2021 at 15:00
  • I think you can do ref={ function(el) { if(el && selectedObjectId === id) { selectedRef.current = el; } } }. Commented Dec 2, 2022 at 16:49

4 Answers 4

20

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 />
  )
}
Sign up to request clarification or add additional context in comments.

1 Comment

I get error: Function components cannot be given refs.
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 />
</>)

}

5 Comments

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.
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.
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!
@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.
Thank yoy very much, still learning a lot, glad there's people like you!
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.

Comments

-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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.