2

I am still confused if I use useRef([]); the right way, as itemsRef returns Object {current: Array[0]}. Here in action: https://codesandbox.io/s/zealous-platform-95qim?file=/src/App.js:0-1157

import React, { useRef } from "react";
import "./styles.css";

export default function App() {
  const items = [
    {
      id: "asdf2",
      city: "Berlin",
      condition: [
        {
          id: "AF8Qgpj",
          weather: "Sun",
          activity: "Outside"
        }
      ]
    },
    {
      id: "zfsfj",
      city: "London",
      condition: [
        {
          id: "zR8Qgpj",
          weather: "Rain",
          activity: "Inside"
        }
      ]
    }
  ];

  const itemsRef = useRef([]);

  // Object {current: Array[0]}
  // Why? Isn't it supposed to be filled with my refs (condition.id)
  console.log(itemsRef);

  return (
    <>
      {items.map(cities => (
        <div key={cities.id}>
          <b>{cities.city}</b>
          <br />
          {cities.condition.map(condition => (
            <div
              key={condition.id}
              ref={el => (itemsRef.current[condition.id] = el)}
            >
              Weather: {condition.weather}
              <br />
              Activity: {condition.activity}
            </div>
          ))}
          <br />
          <br />
        </div>
      ))}
    </>
  );
}

In the original example I receive // Object {current: Array[3]} when I console.log(itemsRef); The difference is that I used in my version itemsRef.current[condition.id] as its a nested map loop and therefore i doesn't work.

import React, { useRef } from "react";
import "./styles.css";

export default function App() {
  const items = ["sun", "flower", "house"];
  const itemsRef = useRef([]);

  // Object {current: Array[3]}
  console.log(itemsRef);

  return items.map((item, i) => (
    <div key={i} ref={el => (itemsRef.current[i] = el)}>
      {item}
    </div>
  ));
}
1
  • Thats certainly not the correct way, whats your end goal to happen ? you might want to create a functional component for weather activity, Map that component with condition as props, use useRef inside that component to reference the div Commented Jun 26, 2020 at 15:01

1 Answer 1

3

You're using non-numeric string keys when adding the refs to itemRefs, which means they end up being properties of the array object, but not array elements, so its length remains 0. Depending on your console, it may or may not show non-element properties on an array object.

You could make them array elements instead by using the index from map (but keep reading!):

{cities.condition.map((condition, index) => (
    <div
        key={condition.id}
        ref={el => (itemsRef.current[index] = el)}
    >
        Weather: {condition.weather}
        <br />
        Activity: {condition.activity}
    </div>
))}

but depending on what you're doing with those refs I would avoid that in favor of making each condition its own component instead:

const Condition = ({weather, activity}) => {
    const itemRef = useRef(null);
  
    return (
        <div
            ref={itemRef}
        >
            Weather: {weather}
            <br />
            Activity: {activity}
        </div>
    );
};

Then get rid of itemRefs and do:

{cities.condition.map(({id, weather, activity}) => (
    <Condition key={id} weather={weather} activity={activity} />
))}

One problem with your current way even if we use array elements is that itemRefs will continue to have three elements in it even when the DOM elements that they used to refer to are gone (they'll have null instead), since React calls your ref callback with null when the element is removed, and your code is just storing that null in the array.

Alternatively, you might use an object:

const itemRefs = useRef({});
// ...
{cities.condition.map(condition => (
    <div
        key={condition.id}
        ref={el => {
            if (el) {
                itemsRef.current[condition.id] = el;
            } else {
                delete itemsRef.current[condition.id];
            }
        }}
    >
        Weather: {condition.weather}
        <br />
        Activity: {condition.activity}
    </div>
))}

Or perhaps a Map:

const itemRefs = useRef(new Map());
// ...
{cities.condition.map(condition => (
    <div
        key={condition.id}
        ref={el => {
            if (el) {
                itemsRef.current.set(condition.id, el);
            } else {
                itemsRef.current.delete(condition.id);
            }
        }}
    >
        Weather: {condition.weather}
        <br />
        Activity: {condition.activity}
    </div>
))}

But again, I'd lean toward making a Condition component that manages its own ref.

Sign up to request clarification or add additional context in comments.

3 Comments

Hey, could you tell me why are you checking for el in case of an object or map?
@Siddharth - See Callback Refs. As I mentioned above, when the DOM element is removed, React calls the callback with null. So the code above branches in order to remove the entry for the element entirely (rather than just setting it null).
Thanks a lot T.J. Crowder for the good answers. Especially your last suggestion with Map was very helpful as I found a package earlier that seems to have the same approach with Map: github.com/Macil/react-multi-ref/blob/master/src/index.js I am not sure yet if the Condition component approach works for my case. I will try if I can rearrange my code. Here you can see my intended use for the ref: codesandbox.io/s/festive-cherry-42tin?file=/src/Parent.js

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.