React state not changing on first select input change

Viewed 97

I am attempting to sort and display information using select element option changes.

The data however does not change until I have changed both select value three times.

What is the reasoning why state won't change on first change? I was wondering if I am required to use a useEffect?

Any guidance would be appreciated.

PS - I have attempted to use useEffect and to listen and rerun on sortDirection and filterBy state changes

My current code:

const Home = () => {
  const [phoneData, setPhoneData] = useState([
    { name: "Samsung", ppm: 50, upfront: 45 },
    { name: "Iphone", ppm: 60, upfront: 100 },
    { name: "Nokia", ppm: 40, upfront: 20 },
  ]);
  const [filteringBy, setFilteringBy] = useState("");
  const [sortDirection, setSortDirection] = useState("");


// sets sortDirection state to either ASC or DESC
  const handleDirectionFilter = (val) => {
    if (val === sortDirection) return;
    console.log(val);
    setSortDirection(val);
    sortPhoneData();
  };

// sets filterBy state to either ppm or upfront
  const handleFilterType = (val) => {
    if (val === filteringBy) return;
    console.log(val);
    setFilteringBy(val);
    sortPhoneData();
  };

// This function should sort data on first call hover takes many calls
  const sortPhoneData = () => {
    let newData;
    filteringBy === "ppm" && sortDirection === "ASC"
      ? (newData = phoneData.sort((a, b) => a.ppm - b.ppm))
      : (newData = phoneData.sort((a, b) => a.ppm + b.ppm));

    filteringBy === "upfront" && sortDirection === "ASC"
      ? (newData = phoneData.sort((a, b) => a.ppm - b.ppm))
      : (newData = phoneData.sort((a, b) => a.ppm + b.ppm));

    // This only changes after several function calls
    setPhoneData(newData);
  };

  return (
    <div className="centre">
      <div">
        <select onChange={(e) => handleDirectionFilter(e.currentTarget.value)}>
          <option value="">Select one</option>
          <option value="DESC">DESC</option>
          <option value="ASC">ASC</option>
        </select>
        <select onChange={(e) => handleFilterType(e.currentTarget.value)}>
          <option value="">Select one</option>
          <option value="ppm">PPM</option>
          <option value="upfront">upfront</option>
        </select>
      </div>
      {phoneData?.map((phone) => (
        <div key={phone.name}>
          <h3>{phone.name}</h3>
          <p>PPM: {phone.ppm}</p>
          <p>Upfront: {phone.upfront}</p>
        </div>
      ))}
      {filteringBy}
      <br />
      {sortDirection}
    </div>
  );
};
2 Answers

You have to use useEffect to listen to filteringBy because the state will not reflect immediately

  useEffect(() => {
    let newData;
    filteringBy === "ppm" && sortDirection === "ASC"
      ? (newData = phoneData.sort((a, b) => a.ppm - b.ppm))
      : (newData = phoneData.sort((a, b) => a.ppm + b.ppm));

    filteringBy === "upfront" && sortDirection === "ASC"
      ? (newData = phoneData.sort((a, b) => a.ppm - b.ppm))
      : (newData = phoneData.sort((a, b) => a.ppm + b.ppm));

    // This only changes after several function calls
    setPhoneData(newData);
  }, [filteringBy]);  // maybe you have to add phoneData here

You have to use useEffect in your code, but this is not your only problem.

  1. Your phoneData depends on both sortDirection and filteringBy. So you need to use 2 useEffect.

  2. In this part of your code you've used ppm instead of upfront for your properties, you should change it to upfront. because you filtered by upfront:

    filteringBy === "upfront" && sortDirection === "ASC"
      ? (newData = phoneData.sort((a, b) => a.ppm - b.ppm))
      : (newData = phoneData.sort((a, b) => a.ppm + b.ppm));
    
  3. In addition sorting with + is not a solution for reverse sorting. You should change second sort to : sort((a, b) => b.ppm - a.ppm).

  4. Also you don't need to assign new sorted array to newData, Because it sorts in its self.

  5. And finally when you're working with array states, its better to make a shallow copy from your state and change the newData. It's prevent further unforeseen behaviour in your code:

    let newData = [...phoneData];
    

Here's the code:

  const sortPhoneData = () => {
    if (filteringBy && sortDirection) {
      let newData = [...phoneData];
      filteringBy === "ppm" && sortDirection === "ASC"
        ? newData.sort((a, b) => a.ppm - b.ppm)
        : newData.sort((a, b) => b.ppm - a.ppm);

      filteringBy === "upfront" && sortDirection === "ASC"
        ? newData.sort((a, b) => a.upfront - b.upfront)
        : newData.sort((a, b) => b.upfront - a.upfront);
      setPhoneData(newData);
    }
  };

  useEffect(() => {
    sortPhoneData();
  }, [filteringBy]);

  useEffect(() => {
    sortPhoneData();
  }, [sortDirection]);

Edit condescending-joliot-mldtu

Related