【问题标题】:useState updates state on second clickuseState 在第二次点击时更新状态
【发布时间】:2021-10-28 05:02:48
【问题描述】:

我已经看到这个问题以前被问过很多次,但没有一个答案对我来说有意义。在尝试了我能找到的所有解决方案之后,我决定自己问。

以下是我的代码,主要问题是在 DOM filteredData 中仅在第二次点击时发生变化。请注意,projs 是包含获取的数据的道具,这些数据会被过滤并显示

  const langs = ["react", "next js", "material-ui", "tailwind css", "firebase"];

  const [projectData, setProjectData] = useState([]);
  const [filteredData, setFilteredData] = useState([]);
  const [category, setCategory] = useState("");

  useEffect(() => {
    setProjectData(projs);
  }, []);

  const handleCategory = (res) => {
    setCategory(res.data);
    const filter = projectData.filter((data) => data.category === category);
    setFilteredData(filter);
  };

按钮:

          {langs.map((data, index) => (
            <button
              key={index}
              class="px-2 sm:px-6 py-2 ring-2 font-semibold ring-portfBtnLight rounded-md transform hover:scale-110 transition duration-500"
              onClick={() => handleCategory({ data })}
            >
              {data}
            </button>
          ))}

我现在很迷茫,任何帮助将不胜感激

【问题讨论】:

    标签: reactjs state use-effect use-state


    【解决方案1】:

    我认为这是问题

    setCategory(res.data);
    const filter = projectData.filter((data) => data.category === category);
    

    您调用setCategory。但这不会改变您在后续行中引用的 category 的直接值。

    我想你想要这个:

    const updatedCategory = res.data;
    setCategory(updatedCategory);
    const filter = projectData.filter((data) => data.category === updatedCategory);
    

    另外,res.data 实际上是您想要使用的“类别”吗?我只是问,因为你在这么多变量、参数和对象成员之间使用data,我不禁怀疑这是否真的是一个错字。

    【讨论】:

    • 是的 res.data 绝对是该类别,因为它可以完美运行,但在第二次点击时。尝试了您的替代方案,问题仍然存在。
    • 而“类别”是一个数字或字符串,对吗?如果是其他类型(数组或对象),那就另当别论了。
    【解决方案2】:

    您不能在同一渲染周期中使用新设置的状态值。要在同一个处理程序中继续调用两个 setState,您需要使用来自 res.data 的类别来生成新的过滤数组,然后设置两者。

    const handleCategory = (res) => {
      const filter = projectData.filter((data) => data.category === res.data);
      setCategory(res.data);
      setFilteredData(filter);
    };
    

    const { useState, useEffect } = React;
    
    function App({ projs }) {
      const langs = ["react", "next js", "material-ui", "tailwind css", "firebase"];
    
      const [projectData, setProjectData] = useState([]);
      const [filteredData, setFilteredData] = useState([]);
      const [category, setCategory] = useState("");
    
      useEffect(() => {
        setProjectData(projs);
      }, [projs]); // need to watch for change as props won't update this automatically. Setting state from props is somewhat of an anti-pattern
    
      const handleCategory = (res) => {
        const filter = projectData.filter((data) => data.category === res.data);
        setCategory(res.data);
        setFilteredData(filter);
      };
    
      return (
        <div className="App">
          {langs.map((data) => (
            <button
              key={data} // avoid using index as key as this will lead to erros in updating.
              class={(data === category ? 'selected ' : '') + "px-2 sm:px-6 py-2 ring-2 font-semibold ring-portfBtnLight rounded-md transform hover:scale-110 transition duration-500"}
              onClick={() => handleCategory({ data })}
            >
              {data}
            </button>
          ))}
          <div>
            <h3>{category}</h3>
            {filteredData.length
              ? filteredData.map(p => (
                <div>
                  <p>{p.value} – {p.category}</p>
                </div>
              ))
              : <p>No projects match</p>}
          </div>
        </div>
      );
    }
    
    const projectData = [{ id: 1, value: 'Project 1', category: 'react' }, { id: 2, value: 'Project 2', category: 'next js' }, { id: 3, value: 'Project 3', category: 'react' }, { id: 4, value: 'Project 4', category: 'material-ui' }]
    
    ReactDOM.render(
      <App projs={projectData} />,
      document.getElementById("root")
    );
    .selected {
      background-color: tomato;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
    
    <div id="root"></div>

    或者,在 useEffect 中更新 filteredData 以监视对 categoryprojectData 的更改

    useEffect(() => {
      setFilteredData(projectData.filter((data) => data.category === category));
    }, [category, projectData]);
    
    const handleCategory = (res) => {
      setCategory(res.data);
    };
    

    const { useState, useEffect } = React;
    
    function App({ projs }) {
      const langs = ["react", "next js", "material-ui", "tailwind css", "firebase"];
    
      const [projectData, setProjectData] = useState([]);
      const [filteredData, setFilteredData] = useState([]);
      const [category, setCategory] = useState("");
    
      useEffect(() => {
        setProjectData(projs);
      }, [projs]); // need to watch for change as props won't update this automatically. Setting state from props is somewhat of an anti-pattern
    
      useEffect(() => {
        setFilteredData(projectData.filter((data) => data.category === category));
      }, [category, projectData]);
    
      const handleCategory = (res) => {
        setCategory(res.data);
      };
    
      return (
        <div className="App">
          {langs.map((data) => (
            <button
              key={data}
              class={(data === category ? 'selected ' : '') + "px-2 sm:px-6 py-2 ring-2 font-semibold ring-portfBtnLight rounded-md transform hover:scale-110 transition duration-500"}
              onClick={() => handleCategory({ data })}
            >
              {data}
            </button>
          ))}
          <div>
            <h3>{category}</h3>
            {filteredData.length
              ? filteredData.map(p => (
                <div>
                  <p>{p.value} – {p.category}</p>
                </div>
              ))
              : <p>No projects match</p>}
          </div>
        </div>
      );
    }
    
    const projectData = [{ id: 1, value: 'Project 1', category: 'react' }, { id: 2, value: 'Project 2', category: 'next js' }, { id: 3, value: 'Project 3', category: 'react' }, { id: 4, value: 'Project 4', category: 'material-ui' }]
    
    ReactDOM.render(
      <App projs={projectData} />,
      document.getElementById("root")
    );
    .selected {
      background-color: tomato;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
    
    <div id="root"></div>

    如 sn-ps 中所述——

    从 props 设置状态有点反模式,但如果你必须将 prop 添加到依赖数组中,以便在 prop 更改时更新状态。 (道具更改不会强制重新安装)。

    另外,避免使用index 作为键,尤其是在映射顺序/成员身份将发生变化的数组时,因为 React 不会按预期更新。

    【讨论】:

    • 就是这样,谢谢!有道理,感谢index / 键的提示
    • @starbvuks - 哪个解决方案有效?因为建议的第一个解决方案似乎与我的相同。还是建议使用useEffect 或解决问题的关键/索引?
    • useEffect 建议
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-11
    • 1970-01-01
    • 2021-11-23
    • 1970-01-01
    相关资源
    最近更新 更多