useState 在第二次点击时更新状态

Posted

技术标签:

【中文标题】useState 在第二次点击时更新状态【英文标题】:useState updates state on second click 【发布时间】: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>
          ))

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

【问题讨论】:

【参考方案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 建议

以上是关于useState 在第二次点击时更新状态的主要内容,如果未能解决你的问题,请参考以下文章

用于获取数据的自定义反应钩子在第二次点击时未提供数据

使用 ref 从父函数组件传递时 useState 不更新数据

React Hooks:useState with onClick 仅更新第二次单击按钮?

我收到此错误:“来自 useState() 和 useReducer() Hooks 的状态更新不支持第二个回调...”当我更改状态时

React useState hook 使用初始状态值并忽略更新

为啥 setState 回调会抛出错误:“来自 useState() 和 useReducer() Hooks 的状态更新不支持第二个回调参数...”