对获取的数据进行排序

Sort fetched data

我需要对获取的数据添加排序 (ascending/descending)。

我从 API 端点获取所有数据。我映射该数组中的每个对象以显示在单独的组件卡中。但是一旦我选择从降序名称对数据进行排序,我就会快速更改组件,如果它们是从 Z 到 A 排序的,但它会立即转换回初始获取状态(从 A 到 Z)。

你能告诉我问题出在哪里吗?我不知道为什么,但感觉排序数组没有保存在我用来映射所有卡片的状态“数据”中。

import { useState } from 'react';
import { useEffect } from 'react';
import './styles/main.scss';
import Card from './components/Card/Card';
import { v4 as uuidv4 } from 'uuid';

function App() {
  const [data, setData] = useState([]);
  const [sortType, setSortType] = useState('default');

  useEffect(() => {
    fetchData();
    sortData();
  }, [sortType]);

  const fetchData = async () => {
    const response = await fetch(
      'https://restcountries.com/v2/all?fields=name,region,area'
    );
    const data = await response.json();
    setData(data);
  };

  function sortData() {
    let sortedData;
    if (sortType === 'descending') {
      sortedData = [...data].sort((a, b) => {
        return b.name.localeCompare(a.name);
      });
    } else if (sortType === 'ascending') {
      sortedData = [...data].sort((a, b) => {
        return a.name.localeCompare(b.name);
      });
    } else {
      return data;
    }
    setData(sortedData);
  }

  return (
    <div className='content'>
      <header className='content__header'>
        <h1>Header placeholder</h1>
      </header>
      <div className='wrapper'>
        <div className='wrapper__sort-buttons'>
          <select
            defaultValue='default'
            onChange={(e) => setSortType(e.target.value)}
          >
            <option disabled value='default'>
              Sort by
            </option>
            <option value='ascending'>Ascending</option>
            <option value='descending'>Descending</option>
          </select>
        </div>
        <ul className='wrapper__list'>
          {data.map((country) => {
            country.key = uuidv4();
            return (
              <li key={country.key}>
                <Card
                  name={country.name}
                  region={country.region}
                  area={country.area}
                />
              </li>
            );
          })}
        </ul>
      </div>
    </div>
  );
}

export default App;

这是我很快得到的:

然后它回到初始状态:

您使用 useEffect 的方式似乎导致您的组件在您每次更改排序类型时重新获取数据。由于多个地方在不同时间更新您的数据状态,这可能会导致竞争条件。

我会将排序逻辑移动到 useMemo 中,并且仅在初始加载时获取 useEffect 中的数据:

import { useEffect, useMemo, useState } from "react";
import './styles/main.scss';
import Card from './components/Card/Card';
import { v4 as uuidv4 } from "uuid";

function App() {
  const [data, setData] = useState([]);
  const [sortType, setSortType] = useState("default");

  // Move sort logic here...
  const sortedData = useMemo(() => {
    let result = data;

    if (sortType === "descending") {
      result = [...data].sort((a, b) => {
        return b.name.localeCompare(a.name);
      });
    } else if (sortType === "ascending") {
      result = [...data].sort((a, b) => {
        return a.name.localeCompare(b.name);
      });
    }

    return result;
  }, [data, sortType]);

  // Only fetch data once on component mount...
  useEffect(() => {
    fetchData();
  }, []);

  const fetchData = async () => {
    const response = await fetch(
      "https://restcountries.com/v2/all?fields=name,region,area"
    );
    const data = await response.json();
    setData(data);
  };

  return (
    <div className="content">
      <header className="content__header">
        <h1>Header placeholder</h1>
      </header>
      <div className="wrapper">
        <div className="wrapper__sort-buttons">
          <select
            defaultValue="default"
            onChange={(e) => setSortType(e.target.value)}
          >
            <option disabled value="default">
              Sort by
            </option>
            <option value="ascending">Ascending</option>
            <option value="descending">Descending</option>
          </select>
        </div>
        <ul className="wrapper__list">
          {/* Use sortedData here instead of data... */}
          {sortedData.map((country) => {
            country.key = uuidv4();
            return (
              <li key={country.key}>
                <Card
                  name={country.name}
                  region={country.region}
                  area={country.area}
                />
              </li>
            );
          })}
        </ul>
      </div>
    </div>
  );
}

export default App;

这是 Codesandbox 中的一个基本示例(我注释掉了您的 styles/card 组件):https://codesandbox.io/s/goofy-tdd-8lio9?file=/src/App.js

发生这种情况的原因可能是设置状态函数本质上是异步的,并且调用 setData 的顺序与您预期的不同。 因此,对于使用 sortType 'default' 的初始调用,您没有注意到任何更改,因为您按原样返回数据。但是一旦您将其更改为 'descending',sortData() 中的 setData()fetchData() 中的 setData() 更早被调用,因此您的状态中已经有数据,您会看到数据发生变化在 UI 中停留片刻,然后调用函数 fetchData 中的 setData() 并将您的数据替换为您从 API 调用中获得的未排序或升序的数据订单。

可能的解决方案

不要在 fetchData 方法中设置状态,而只需在 sortData 方法中设置一次,因为无论如何你都需要它。 所以你的代码看起来像这样:

  // we will call sortData inside fetchData so remove it from here
  useEffect(() => {
    fetchData();
  }, [sortType]);

  const fetchData = async () => {
    const response = await fetch(
      'https://restcountries.com/v2/all?fields=name,region,area'
    );
    const data = await response.json();
    // using API response data as an input to sortData function
    sortData(data)
  };

  // using data from parameter instead of state
  function sortData(data) {
    let sortedData;
    if (sortType === 'descending') {
      sortedData = [...data].sort((a, b) => {
        return b.name.localeCompare(a.name);
      });
    } else if (sortType === 'ascending') {
      sortedData = [...data].sort((a, b) => {
        return a.name.localeCompare(b.name);
      });
    } else {
      return data;
    }
    setData(sortedData);
  }

改进

您的 API 调用不依赖于 SORTING ORDER,因此您不需要一次又一次地调用 API,只需调用一次,然后对数据进行排序值从下拉菜单更改。

  // call the API on initial load only
  useEffect(() => {
    fetchData();
  }, []);

  // and on sortType change you can handle it like this:
  useEffect(() => {
    sortData(data);
  }, [sortType]);

  // and using this approach you can use the exact same code for both functions implementation that you posted in your question above.