reactjs - How to create a vector of references and pass it to children components? - Stack Overflow

admin2025-04-21  2

I have a parent component which uses N children components, where N depends on the props in input. For example:

function Parent({ cards }: { cards: Cards[] }) {
  return (
    <>
      {cards.map((value, index) => 
        <ChildComponent ref={TODO} value={value} key={value.id} />
      }
    </>
}

Since I have a bunch of callbacks in the components (not shown) that need to know the location on the screen of each child, I need to create a bunch of refs in the parent component and pass them to the children, so that later I can use refs[n].current.getBoundingClientRect() in one of my callbacks.

All the refs need to be recalculated whenever cards change (cards is the state of an GrandParent component, modified as usual with setState(newCards)).

I already tried many things, but nothing really seems to work.

const cardsRefs = useRef<React.RefObject<HTMLDivElement>[]>([]);
if (cardsRefs.current.length !== cards.length) {
  cardsRefs.current = Array(cards.length)
    .fill(null)
    .map((_, i) => createRef());
}

...

<ChildComponent ref={cardsRefs.current[index]} value={value} key={value.id} />

This sort of works, but I think it's incorrect - if the cards array changes (keeping the same length), I do need to have new refs, right? Also the use of createRef sounds weird.

Another try:

const cardsRefs = useRef<React.RefObject<HTMLDivElement>[]>([]);
...
<ChildComponent
  ref={(el) => cardsRef.current[index] = el}
  value={value}
  key={value.id}
/>

I have no idea how this is meant to work, or why. In any case it does not for me. I get variations of the same error message:

Type 'HTMLDivElement | null' is not assignable to type 'RefObject'. Type 'null' is not assignable to type 'RefObject'

This happens with useRef<React.RefObject<HTMLDivElement>[]>([]), useRef<HTMLDivElement[]>([]), useRef([]) - where the error complains about different types every time. Not sure what type this is meant to be.

Another try:

const cardsRefs = useRef<React.RefObject<HTMLDivElement>[]>([]);

useEffect(() => {
  cardsRefs.current = Array(cards.length)
    .fill(null)
    .map((_, i) => createRef<HTMLDivElement>());
}, [cards]);

This does not work at all, I suspect because useEffect runs after the DOM has been constructed and therefore the ref to the child components have already been passed (and are undefined).

const cardsRefs = useMemo(
  () => useRef<React.RefObject<HTMLDivElement>[]>([]),
  [cards]
)

This does not work because I cannot call useRef inside useMemo.

Ok so basically I don't know what's the right solution here.

I have a parent component which uses N children components, where N depends on the props in input. For example:

function Parent({ cards }: { cards: Cards[] }) {
  return (
    <>
      {cards.map((value, index) => 
        <ChildComponent ref={TODO} value={value} key={value.id} />
      }
    </>
}

Since I have a bunch of callbacks in the components (not shown) that need to know the location on the screen of each child, I need to create a bunch of refs in the parent component and pass them to the children, so that later I can use refs[n].current.getBoundingClientRect() in one of my callbacks.

All the refs need to be recalculated whenever cards change (cards is the state of an GrandParent component, modified as usual with setState(newCards)).

I already tried many things, but nothing really seems to work.

const cardsRefs = useRef<React.RefObject<HTMLDivElement>[]>([]);
if (cardsRefs.current.length !== cards.length) {
  cardsRefs.current = Array(cards.length)
    .fill(null)
    .map((_, i) => createRef());
}

...

<ChildComponent ref={cardsRefs.current[index]} value={value} key={value.id} />

This sort of works, but I think it's incorrect - if the cards array changes (keeping the same length), I do need to have new refs, right? Also the use of createRef sounds weird.

Another try:

const cardsRefs = useRef<React.RefObject<HTMLDivElement>[]>([]);
...
<ChildComponent
  ref={(el) => cardsRef.current[index] = el}
  value={value}
  key={value.id}
/>

I have no idea how this is meant to work, or why. In any case it does not for me. I get variations of the same error message:

Type 'HTMLDivElement | null' is not assignable to type 'RefObject'. Type 'null' is not assignable to type 'RefObject'

This happens with useRef<React.RefObject<HTMLDivElement>[]>([]), useRef<HTMLDivElement[]>([]), useRef([]) - where the error complains about different types every time. Not sure what type this is meant to be.

Another try:

const cardsRefs = useRef<React.RefObject<HTMLDivElement>[]>([]);

useEffect(() => {
  cardsRefs.current = Array(cards.length)
    .fill(null)
    .map((_, i) => createRef<HTMLDivElement>());
}, [cards]);

This does not work at all, I suspect because useEffect runs after the DOM has been constructed and therefore the ref to the child components have already been passed (and are undefined).

const cardsRefs = useMemo(
  () => useRef<React.RefObject<HTMLDivElement>[]>([]),
  [cards]
)

This does not work because I cannot call useRef inside useMemo.

Ok so basically I don't know what's the right solution here.

Share Improve this question edited Jan 23 at 0:26 Drew Reese 204k18 gold badges246 silver badges273 bronze badges asked Jan 22 at 20:29 AntAnt 5,4242 gold badges28 silver badges48 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 1

Your approaches are close but require minor tweaks.

Approach 1 - Precomputing the HTMLDivElement Refs

You are overcomplicating things a bit by trying to use the useEffect or useMemo hooks. You can simply compute the refs as needed, using the createRef utility for the refs you are storing in the cardsRefs.

In other words, cardsRefs is a React ref that stores an array of React.RefObject<HTMLDivElement> refs.

Refs are mutable buckets, so map the cards array to previously created refs, or create new refs as necessary.

const cardsRefs = useRef<React.RefObject<HTMLDivElement>[]>([]);

cardsRefs.current = cards.map((_, i) => cardsRefs.current[i] || createRef());

Because you are storing an array of React refs, you not only need to access the current value of cardsRefs, but also that of any array element ref.

cardsRefs.current[n].current?.getBoundingClientRect();
          ^^^^^^^    ^^^^^^^

Parent

function Parent({ cards }: { cards: Cards[] }) {
  const cardsRefs = useRef<React.RefObject<HTMLDivElement>[]>([]);
  cardsRefs.current = cards.map((_, i) => cardsRefs.current[i] || createRef());

  const someCallback = () => {
    cardsRefs.current[n].current?.getBoundingClientRect();
  };

  return (
    <>
      {cards.map((value, index) => (
        <ChildComponent
          ref={cardsRefs.current[index]}
          value={value}
          key={value.id}
        />
      ))}
    </>
  );
}

Approach 2 - Using the Legacy Ref Callback Syntax

When using the legacy callback syntax the Refs can potentially be null, which is what the error informs you of.

Type 'HTMLDivElement | null' is not assignable to type 'RefObject'. Type 'null' is not assignable to type 'RefObject'

It tells you exactly what the cardsRef type needs to be, e.g. an array of HTMLDivElement | null.

const cardsRefs = useRef<(HTMLDivElement | null)[]>([]);

The difference here is that you are not storing an array of React refs, but instead an array of references to an HTML div element, or null, so the callback access will be a bit different.

cardsRefs.current[n]?.getBoundingClientRect();
          ^^^^^^^

Parent

function Parent({ cards }: { cards: Cards[] }) {
  const cardsRefs = useRef<(HTMLDivElement | null)[]>([]);

  const someCallback = () => {
    cardsRefs.current[n]?.getBoundingClientRect();
  };

  return (
    <>
      {cards.map((value, index) => (
        <ChildComponent
          ref={(el) => (cardsRefs.current[index] = el)}
          value={value}
          key={value.id}
        />
      ))}
    </>
  );
}

You're on the right track, both in terms of thinking and in terms of approach.

It's correct to use an array to store the refs for elements. There are some issues though with your implementation.

When you use the useRef hook. The generic associated with the function represents the type of ref you want to represent; in react this will usually be Element, HTMLElement or some perhaps some other type assignable to one of these. If not dealing with DOM it could be anything but in your case it should be something like this. But in your implementation you are setting the generic as RefObject<HTMLDivElement>. The implementation of useRef is likely similar to function useRef<T>(initialValue: T): RefObject<T>; so the return type is already wrapped in RefObject. The generic you provide should therefore only be HTMLDivElement.

Since on first evaluation of the hook the ref could be null since the element is not rendered you, you should check for that before assigning to the ref. You can use callback refs to do this easily so in your ref assignment:

<ChildComponent ref={TODO} value={value} key={value.id} />

You should check the ref is not null, something like ref={el=>el?el:undefined} if original useRef accepts undefined. Or something along those lines, depending on how you defined the ref.

When the element is changed, the ref should update if it was implemented correctly.

转载请注明原文地址:http://anycun.com/QandA/1745229559a90514.html