在React中从props设置状态不起作用

huangapple go评论127阅读模式
英文:

Setting state in React from props doesn't work

问题

最近,我在使用React时遇到了一个问题 - 在我看来,这个问题相当奇怪。

import React, { useState } from "react";
import Person from "./Person";
import Filter from "./Filter";

function People(props) {
  const [people, setPeople] = useState(props.people);

  function filterPeople(name) {
    let people = people.filter((person) =>
      person.name.toLowerCase().startsWith(name.toLowerCase())
    );
    setPeople(people);
  }

  return (
    <div>
      <Filter filter={filterPeople} />
      {people.map((person, id) => (
        <Person key={id} person={person} />
      ))}
    </div>
  );
}

export default People;

在上面的代码中,我试图从props中的people(第6行)设置状态people,但它总是将其设置为空数组,尽管我从父组件传递的people prop不是空的。更奇怪的是,当我使用console.log(props.people)时,它显示了正确的数据(一个包含3个元素的数组)!如果有人能够解释我的实现出了什么问题,那将非常有帮助。

英文:

Recently, I was playing with React and got stuck in - what seems to me, a fairly strange problem.

import React, { useState } from &quot;react&quot;;
import Person from &quot;./Person&quot;;
import Filter from &quot;./Filter&quot;;

function People(props) {
  const [people, setPeople] = useState(props.people);

  function filterPeople(name) {
    let people = people.filter((person) =&gt;
      person.name.toLowerCase().startsWith(name.toLowerCase())
    );
    setPeople(people);
  }

  return (
    &lt;div&gt;
      &lt;Filter filter={filterPeople} /&gt;
      {people.map((person, id) =&gt; (
        &lt;Person key={id} person={person} /&gt;
      ))}
    &lt;/div&gt;
  );
}

export default People;

In the code above, I'm trying to set the state people from prop people (in line 6), but it's always setting it to an empty array even though the people prop I'm passing from the parent isn't empty. The more strange thing is, when I console.log(props.people), it shows currect data (which is an array of 3 element)! It would be really helpful if someone could explain what's going on with my implementation.

答案1

得分: 2

根据问题中的评论,您似乎正在复制状态。也就是说,父组件在状态中维护了 people,而这个 People 组件在状态中维护了 people

这意味着当 props 发生变化时,这个组件仍然会使用自己的本地状态。

没有额外的上下文...不要这样做。如果目标是使用传递的 props,只需使用它:

function People(props) {
  
  function filterPeople(name) {
    const filteredPeople = props.people.filter((person) =>
      person.name.toLowerCase().startsWith(name.toLowerCase())
    );
    props.setPeople(filteredPeople);
  }

  return (
    <div>
      <Filter filter={filterPeople} />
      {props.people.map((person, id) => (
        <Person key={id} person={person} />
      ))}
    </div>
  );
}

这将不仅需要将 people 作为 prop 传递,还需要传递 setPeople


或者: 仍然没有上下文,但也许您实际上不想更新父组件的状态,而是想在此组件中仅在本地“过滤” people 数组。在这种情况下,您可以在渲染中直接跟踪 name 值的状态并进行过滤。例如:

function People(props) {
  const [name, setName] = useState('');

  const filteredPeople = props.people.filter((person) =>
    person.name.toLowerCase().startsWith(name.toLowerCase())
  );

  return (
    <div>
      <Filter filter={setName} />
      {filteredPeople.map((person, id) => (
        <Person key={id} person={person} />
      ))}
    </div>
  );
}

在大多数情况下,这不会导致足够大的性能损耗,但如果确实存在性能问题,您还可以对 filteredPeople 进行 memoization:

function People(props) {
  const [name, setName] = useState('');

  const filterPeople = useMemo(() => {
    return props.people.filter((person) =>
      person.name.toLowerCase().startsWith(name.toLowerCase())
    );
  }, [props.people, name]);

  const filteredPeople = filterPeople();

  return (
    <div>
      <Filter filter={setName} />
      {filteredPeople.map((person, id) => (
        <Person key={id} person={person} />
      ))}
    </div>
  );
}

或者: 如果您计划稍后在原始状态的本地副本上执行进一步的操作而不修改父状态,可以保持状态复制。在这种情况下,如果希望在 props 发生更改时随时更新本地状态,可以使用 useEffect

function People(props) {
  const [people, setPeople] = useState(props.people);

  useEffect(() => {
    setPeople(props.people);
  }, [props.people]);

  function filterPeople(name) {
    let filteredPeople = people.filter((person) =>
      person.name.toLowerCase().startsWith(name.toLowerCase())
    );
    setPeople(filteredPeople);
  }

  return (
    <div>
      <Filter filter={filterPeople} />
      {people.map((person, id) => (
        <Person key={id} person={person} />
      ))}
    </div>
  );
}

这将在父组件发送不同的 props.people 值时有效地覆盖对本地状态所做的任何更改。

英文:

Based on comments on the question, you appear to be duplicating state. That is, the parent component maintains people in state, and this People component also maintains people in state.

Which means that when the props change, this component will still be using its own local state.

Without additional context... don't do that. If the goal is to use whatever is passed in props, just use that:

function People(props) {

  function filterPeople(name) {
    const filteredPeople = people.filter((person) =&gt;
      person.name.toLowerCase().startsWith(name.toLowerCase())
    );
    props.setPeople(filteredPeople);
  }

  return (
    &lt;div&gt;
      &lt;Filter filter={filterPeople} /&gt;
      {people.map((person, id) =&gt; (
        &lt;Person key={id} person={person} /&gt;
      ))}
    &lt;/div&gt;
  );
}

This would require not only passing people as a prop, but also setPeople.


Alternatively: Still without context, but maybe you don't want to actually update the parent component's state but instead want to only locally "filter" the people array in this component. In that case you could track that name value in state and filter directly in the rendering. For example:

function People(props) {
  const [name, setName] = useState(&#39;&#39;);

  const filteredPeople = people.filter((person) =&gt;
    person.name.toLowerCase().startsWith(name.toLowerCase())
  );

  return (
    &lt;div&gt;
      &lt;Filter filter={setName} /&gt;
      {filteredPeople.map((person, id) =&gt; (
        &lt;Person key={id} person={person} /&gt;
      ))}
    &lt;/div&gt;
  );
}

In most cases this won't incur a significant enough performance penalty to worry about, but if it does then you can also memo-ize filteredPeople:

function People(props) {
  const [name, setName] = useState(&#39;&#39;);

  const filterPeople = useMemo(() =&gt; {
    return people.filter((person) =&gt;
      person.name.toLowerCase().startsWith(name.toLowerCase())
    );
  }, [people, name]);

  const filteredPeople = filterPeople();

  return (
    &lt;div&gt;
      &lt;Filter filter={setName} /&gt;
      {filteredPeople.map((person, id) =&gt; (
        &lt;Person key={id} person={person} /&gt;
      ))}
    &lt;/div&gt;
  );
}

Alternatively: You could keep state duplicated if you plan to perform further operations at a later time on a localized copy of the original state without modifying the parent state. In that case if you want to update the local state any time the prop changes, you can do that with useEffect:

function People(props) {
  const [people, setPeople] = useState(props.people);

  useEffect(() =&gt; {
    setPeople(props.people);
  }, [props.people]);

  function filterPeople(name) {
    let people = people.filter((person) =&gt;
      person.name.toLowerCase().startsWith(name.toLowerCase())
    );
    setPeople(people);
  }

  return (
    &lt;div&gt;
      &lt;Filter filter={filterPeople} /&gt;
      {people.map((person, id) =&gt; (
        &lt;Person key={id} person={person} /&gt;
      ))}
    &lt;/div&gt;
  );
}

This would effectively over-write whatever changes have been made to local state any time the parent component sends a different value for props.people.

huangapple
  • 本文由 发表于 2023年8月11日 01:37:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/76878114.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定