json - React JS 组件未在状态更改时更新

标签 json reactjs

我一直在尝试实现一种方法,您可以通过切换更改状态的选择元素,以不同的方式对排行榜进行排序,从而导致组件重新呈现。

问题是,它可以正确地对默认值进行排序,但是每当我将选择值从默认值更改为“z-to-a”时,它似乎没有更新。

注意:我添加了一些 console.log 语句,它们的行为似乎很奇怪。

我的 JSX:

import React, { useState, useEffect } from 'react';
import './Leaderboard.css';
import LbRow from '../../components/LbRow/LbRow'; /* A row in the leaderboard*/
import points from '../../data/tree-points.json';

function Leaderboard() {
    // Initialize the points as the data that we passed in
    const [state, setState] = useState({
         points: points,
         sortBy: "first-to-last"
    });

    // Changes the sort method used by the leaderboard
    const changeSortBy = (event) => {
        var newSort = event.target.value;

        // Sorts the data differently depending on the select value
        switch(newSort) {
            case "first-to-last":
                sortDescending("points","first-to-last");
                break;
            case "z-to-a":
                sortDescending("tree_name","z-to-a");
                console.log(state.points.treePoints); // Logs incorrectly, still logs the same array as in "first-to-last"
                break;
            default:
                sortDescending("points","first-to-last");
        }

        // Re-renders the component with new state
        setState({
            points: state.points,
            sortBy: newSort
        });

    }

    /* Updates the leaderboard state to be in descending point order */
    const sortDescending = (aspect, sortMethod) => {

        console.log(sortMethod); // Logs correctly

        // Sorts the data in descending points order
        let sortedPoints = [...state.points.treePoints].sort((tree1, tree2) => {
            if (tree1[aspect] > tree2[aspect]) { return -1; }
            if (tree1[aspect] < tree2[aspect]) { return 1; }
            return 0;
        });

        // Actually updates the state
        setState({
            points: {
                ...state.points,
                treePoints: sortedPoints
            },
            sortBy: sortMethod
        });

        console.log(sortedPoints); // Logs correctly

    };

    /* Calls sortLb on component mount */
    useEffect(() =>{
            sortDescending("points", "first-to-last");
        } 
    ,[]);

    // Attributes used for rendering the leaderboard body
    var rank = 0;
    const sortedData = state.points;

    /* Basically all the active trees with the first tree having the top rank */
    const lbBody = sortedData.treePoints.map((sortedData) => {
        return (
            sortedData.active &&
            <LbRow rank={++rank} tree_name={sortedData.tree_name} points={sortedData.points} active={sortedData.active}/>
        );
    });

    return (
        <div>
            <div className="filters">
                {/* Allows user to sort by different methods */}
                <label htmlFor="sortBy">Sort by:</label>
                <select name="sortBy" className="sortBy" value={state.sortBy} onChange={changeSortBy}>
                    <option value="first-to-last">First to Last</option>
                    <option value="z-to-a">Z to A</option>
                </select>
            </div>
            {/* The table with sorted content */}
            <div className="table">
                {lbBody}
            </div>
        </div>
    );
}

export default Leaderboard;

我真的对这种行为感到困惑,特别是因为我有正确排序的值并且据说已经更新了状态。什么可能导致这种情况发生?谢谢

最佳答案

有3件事必须注意

  • 状态更新是批量的,即。当您在一个函数中多次调用 setState 时,它​​们的结果将被批处理在一起并触发一次重新渲染
  • 状态更新受闭包约束,仅在下一次重新渲染时反射(reflect),而不是在调用状态更新程序后立即反射(reflect)
  • 带有钩子(Hook)的状态更新不会合并,您确实需要自己不断合并状态中的所有值

现在,由于您希望调用状态更新程序两次,您不妨使用回调方法,这将保证来自多个 setState 调用的状态值不会合并,因为您不需要它们。此外,您必须仅更新您想要的字段

function Leaderboard() {
  // Initialize the points as the data that we passed in
  const [state, setState] = useState({
    points: points,
    sortBy: "first-to-last"
  });

  // Changes the sort method used by the leaderboard
  const changeSortBy = (event) => {
    var newSort = event.target.value;

    // Sorts the data differently depending on the select value
    switch (newSort) {
      case "first-to-last":
        sortDescending("points", "first-to-last");
        break;
      case "z-to-a":
        sortDescending("tree_name", "z-to-a");
        break;
      default:
        sortDescending("points", "first-to-last");
    }

    // Re-renders the component with new state
    setState(prev => ({
      ...prev,
      sortBy: newSort // overrider just sortByField
    }));

  }

  /* Updates the leaderboard state to be in descending point order */
  const sortDescending = (aspect, sortMethod) => {

    console.log(sortMethod); // Logs correctly

    // Sorts the data in descending points order
    let sortedPoints = [...state.points.treePoints].sort((tree1, tree2) => {
      if (tree1[aspect] > tree2[aspect]) {
        return -1;
      }
      if (tree1[aspect] < tree2[aspect]) {
        return 1;
      }
      return 0;
    });

    // Actually updates the state
    setState(prev => ({
      ...prev,
      points: {
        ...state.points,
        treePoints: sortedPoints
      },
    }));

  };

  /* Calls sortLb on component mount */
  useEffect(() => {
    sortDescending("points", "first-to-last");
  }, []);

  // Attributes used for rendering the leaderboard body
  var rank = 0;
  const sortedData = state.points;

  ...
}

export default Leaderboard;

处理这个问题以避免复杂的另一个更好的方法是将你的状态分成两个 useState

function Leaderboard() {
    // Initialize the points as the data that we passed in
    const [points, setPoints] = useState(points);
    const [sortBy, setSortBy] = useState(sortBy);

    // Changes the sort method used by the leaderboard
    const changeSortBy = (event) => {
        var newSort = event.target.value;

        // Sorts the data differently depending on the select value
        switch(newSort) {
            case "first-to-last":
                sortDescending("points","first-to-last");
                break;
            case "z-to-a":
                sortDescending("tree_name","z-to-a");
                console.log(state.points.treePoints); // Logs incorrectly, still logs the same array as in "first-to-last"
                break;
            default:
                sortDescending("points","first-to-last");
        }

        // Re-renders the component with new state
        setSortBy(newSort);

    }

    /* Updates the leaderboard state to be in descending point order */
    const sortDescending = (aspect, sortMethod) => {

        console.log(sortMethod); // Logs correctly

        // Sorts the data in descending points order
        let sortedPoints = [...state.points.treePoints].sort((tree1, tree2) => {
            if (tree1[aspect] > tree2[aspect]) { return -1; }
            if (tree1[aspect] < tree2[aspect]) { return 1; }
            return 0;
        });

        // Actually updates the state
        setPoints({
                ...state.points,
                treePoints: sortedPoints
        });

        console.log(sortedPoints); // Logs correctly

    };

    /* Calls sortLb on component mount */
    useEffect(() =>{
            sortDescending("points", "first-to-last");
        } 
    ,[]);

    // Attributes used for rendering the leaderboard body
    var rank = 0;
    const sortedData = points;

    /* Basically all the active trees with the first tree having the top rank */
    const lbBody = sortedData.treePoints.map((sortedData) => {
        return (
            sortedData.active &&
            <LbRow rank={++rank} tree_name={sortedData.tree_name} points={sortedData.points} active={sortedData.active}/>
        );
    });

    return (
        <div>
            <div className="filters">
                {/* Allows user to sort by different methods */}
                <label htmlFor="sortBy">Sort by:</label>
                <select name="sortBy" className="sortBy" value={sortBy} onChange={changeSortBy}>
                    <option value="first-to-last">First to Last</option>
                    <option value="z-to-a">Z to A</option>
                </select>
            </div>
            {/* The table with sorted content */}
            <div className="table">
                {lbBody}
            </div>
        </div>
    );
}

export default Leaderboard;

关于json - React JS 组件未在状态更改时更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61970933/

相关文章:

javascript - 单击输入 : Material-Ui 时保持按钮聚焦

javascript - 无法使用触控板在 Firefox 上滚动 div。在 Chrome 上工作

javascript - 更新 reducer 中对象的状态

javascript - 在 Redux Reactjs 中输入内容时 TextField 失去焦点

android - 解析JSONArray并在android中的ListView中显示数据

c# - 在 Newtonsoft JSON.NET 模式中禁用空类型

json - 如何在hadoop HDFS中解压缩文件

javascript - 从 JSON 转换为对象数组

python - GCP Pub/Sub 和 Python - 如何从消息中获取 JSON key ?

reactjs - 如何突出显示 material-ui TextField 中的部分文本