1

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>
  );
};
DGB
  • 1,252
  • 2
  • 16
  • 32

2 Answers2

3

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
Micko Magallanes
  • 261
  • 1
  • 12
2

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

Majid M.
  • 4,467
  • 1
  • 11
  • 29
  • Thanks I understand. Can I ask why your'e using 2 useEffects? Would the behaviour not be the same if you just used 1 and had both filteringBy and sortDirection in the dependency array. Like this: useEffect(() => { sortPhoneData(); }, [filteringBy, sortDirection]); – DGB Aug 11 '21 at 12:44
  • @DBG Yes, it's possible. I just wrote with this way for better understanding. :) – Majid M. Aug 11 '21 at 14:43