React JS组件不会在状态更改时更新

时间:2020-05-23 10:54:37

标签: json reactjs

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

问题在于,它可以正确排序默认值,但是每当我将select的值从默认值更改为“ 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;

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

1 个答案:

答案 0 :(得分:1)

您必须注意三件事

  • 批处理状态更新,即当您在一个函数中多次调用setState时,它们的结果将被批处理在一起,并且一次会触发一次重新渲染
  • 状态更新受闭包的约束,只会反映在下一次重新渲染中,而不会在调用状态更新器后立即反映出来
  • 带有钩子的状态更新不会合并到您,您需要自己继续合并状态中的所有值

现在,由于您希望调用状态更新程序两次,因此不妨使用回调方法,该方法将确保不会合并来自多个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;