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

问题:

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:

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.

  • 发表于 2020-06-27 22:55
  • 阅读 ( 300 )
  • 分类:sof

条评论

请先 登录 后评论
不写代码的码农
小编

篇文章

作家榜 »

  1. 小编 文章
返回顶部
部分文章转自于网络,若有侵权请联系我们删除