简体   繁体   中英

Generate multiple refs in map loop

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>
  ));
}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM