darius.maximus
darius.maximus

Reputation: 49

useState() in react does not update the data - is this the sort() issue?

when I click on the button to sort the data about countries after the website is loaded then the data displays correclty. But when I want to sort it again by different value, the data doesn't update and the state also doesn't update, however, the function works well, as I can see that the results in the console are sorted correctly. What can be the reason for this weird behaviour? Thank you for your time.

 function AllCountries({ countries }) {
  const [sortedCountries, setSortedCountries] = useState([]);

  useEffect(() => {
      console.log(sortedCountries)
  }, [sortedCountries])

  const sortResults = (val) => {
    let sortedResults = countries.sort((a, b) => {
      if (val === "deaths") {
        return b.TotalDeaths - a.TotalDeaths;
      } else if (val === "cases") {
        return b.TotalConfirmed - a.TotalConfirmed;
      } else if (val === "recovered") {
        return b.TotalRecovered - a.TotalRecovered;
      }
    });
    console.log(sortedResults);
    setSortedCountries(sortedResults);
  };

  return (
    <div className="all-container">
      <p>Sort countries by the highest number of:</p>
      <button onClick={() => sortResults("deaths")}>Deaths</button>
      <button onClick={() => sortResults("cases")}>Cases</button>
      <button onClick={() => sortResults("recovered")}>Recoveries</button>
      <ul className="all-countries">
        {sortedCountries.map((country, key) => {
          return <li key={key}>{country.Country}</li>;
        })}
      </ul>
    </div>
  );
}

export default AllCountries;

Upvotes: 2

Views: 927

Answers (3)

let sortedResults = countries.sort

this line is sorting the countries props and setting sortedResults to that pointer

Let us assume that pointer is held at mem |0001| for simplicity

After your first click, the function is fired, the prop is sorted, and sortedResults is set via setSortedCountries (set state).

This fires off the render that you desire, because the previous state was undefined, and now the state is pointing to |0001|

When your function runs again, the sort function fires off, does its work on |0001| and returns -- you guessed it -- |0001| but with your new sorted array.

When you go to set the state a second time, there wasn't actually any state changed because the previous country is |0001| and the country you want to change to is |0001|

So what can we do my good sir?

First I need you to think about the problem for a second, think about how you could solve this and try to apply some changes.

What you can try to do is copy the countries props to a new array pointer with the same values, and then sort it, and then set sortedCountries state to that list.

Does that make sense?

By the way, this is for similar reasons why if you try to setState directly on an new object state, the new object state will be exactly equal to the one you set. There isn't any real magic going on here. React does not automagically merge your previous object state and your new one, unless you tell it explicitly to do so.

In some sense you have told react to check differences between two states, an old country and a new country state (behind the scenes with the Vdom), but to react, those two states have no difference. And since the two object states have no difference, there will be no actual DOM changes.

You setting a pointer twice will therefore produce no actual changes.

You must therefore set state to an actual new pointer with new values, so the react virtual dom can compare the two states (the previous countries list) and the new countries list.

Hope that helps

-

Upvotes: 1

Array.sort() method doesn't return new reference, it returns the same reference. So I assume in sortedCountries state is stored the same props.countries reference, that is why second button click doesn't set the state (it is the same reference), I can give you simple solution just change this line setSortedCountries(sortedResults) with this setSortedCountries([...sortedResults]) in this case copy of the sortedResults will be passed to setSortedCountries (new reference).

Upvotes: 5

Ali Torki
Ali Torki

Reputation: 2028

It's a bit problem in React and another solution is by using the useReducer instead. such as below:

function reducer(state, action) {
   return [...state, ...action];
}
 function AllCountries({ countries }) {
  const [sortedCountries, setSortedCountries] = useReducer(reducer, []);

  useEffect(() => {
      console.log(sortedCountries)
  }, [sortedCountries])

  const sortResults = (e, val) => {
      e.preventDefault();
    let sortedResults = countries.sort((a, b) => {
      if (val === "deaths") {
        return b.TotalDeaths - a.TotalDeaths;
      } else if (val === "cases") {
        return b.TotalConfirmed - a.TotalConfirmed;
      } else if (val === "recovered") {
        return b.TotalRecovered - a.TotalRecovered;
      }
    });
    console.log(sortedResults);
    setSortedCountries(sortedResults);
  };

  return (
    <div className="all-container">
      <p>Sort countries by the highest number of:</p>
      <button onClick={(e) => sortResults(e, "deaths")}>Deaths</button>
      <button onClick={(e) => sortResults(e, "cases")}>Cases</button>
      <button onClick={(e) => sortResults(e, "recovered")}>Recoveries</button>
      <ul className="all-countries">
        {sortedCountries.map((country, key) => {
          return <li key={key}>{country.Country}</li>;
        })}
      </ul>
    </div>
  );
}

export default AllCountries;

Upvotes: 0

Related Questions