0

I am trying to figure out a way to do filter by price and name, but somehow the useState won't update. I printed the result out and it worked on both checkboxes. By the way, is it better to use onChange or onClick on checkbox, both look the same to me. I have watched many tutorials and searched for many possible solutions but I still can't solve this problem.

let product = [
    {
      name: "tesla",
      cost: 500,
    },
    {
      name: "benz",
      cost: 1000,
    },
    {
      name: "honda",
      cost: 200,
    },

    {
      name: "apple",
      cost: 400,
    },
  ];

  const [data, setData] = useState(product);

  const sortbyprice = () => {
    const result = product.sort((a, b) => {
      return a.cost > b.cost ? 1 : -1;
    });
    setData(result);
    console.log(result);
  };

  const sortbyname = () => {
    const result = product.sort((a, b) => {
      return a.name > b.name ? 1 : -1;
    });

    setData(result);
    console.log(data);
  };

  return (
    <div className="App">
      <div className="sort price">
        <h3>Sort by price</h3>
        <input
          type="checkbox"
          className="searchbar"
          onChange={sortbyprice}
        ></input>
      </div>
      <div className="sort name">
        <h3>Sort by name</h3>
        <input
          type="checkbox"
          className="searchbar"
          onChange={sortbyname}
        ></input>
      </div>

      <div className="product-container">
        {data.map((item, index) => {
          return (
            <div className="product" key={index}>
              <h2>{item.name}</h2>
              <p>{item.cost}</p>
            </div>
          );
        })}
      </div>
    </div>
  );
}
Apostolos
  • 10,033
  • 5
  • 24
  • 39
Sam
  • 21
  • 2

3 Answers3

2

I'm updating my answer in order to cover both cases where product is inside the component or outside.

The problem is the array mutation

You can read more here How can you sort an array without mutating the original array?

The correct way is the following and it should work

 const sortbyprice = () => {
    let dataClone = [...data];
    const result = dataClone.sort((a, b) => {
      return a.cost > b.cost ? 1 : -1;
    });
    setData(result);
    console.log(result);
  };

  const sortbyname = () => {
    let dataClone = [...data];
    const result = dataClone.sort((a, b) => {
      return a.name > b.name ? 1 : -1;
    });

Check this sandbox

Apostolos
  • 10,033
  • 5
  • 24
  • 39
  • Should it make any difference if he use `data` or `product` as long as he call `setData`? – Alan Omar Jun 23 '22 at 07:48
  • hmm you are correct but i think it is a combination of this and `product` being set inside the component so in every render it will be reset. if the snippet of course is all inside the component – Apostolos Jun 23 '22 at 07:54
  • No it's about `onChange` not being triggered. – Alan Omar Jun 23 '22 at 07:58
  • sometimes the trivial - and most important - parts are the ones we dont see! it was the array mutation (Along of course with all the others) – Apostolos Jun 23 '22 at 08:04
  • I tried but it doesn't work for me – Sam Jun 23 '22 at 08:13
  • it does the sorting for "one direction". i didnt implement the "checked/unchecked" part only the part that it sorts as one direction. then you can change it accordingly based on checked value – Apostolos Jun 23 '22 at 08:14
  • @Sam please accept the answer if this helped you. thnx! – Apostolos Jun 23 '22 at 08:44
1

You have to use two different state variables for the this check boxes, and when clicking on one of these you have to update your state accordingly here is CodesandBox.

Edit awesome-maxwell-wd0rz2

Alan Omar
  • 4,023
  • 1
  • 9
  • 20
  • thanks it works for me. So the problem of what is causing the useState not updating is I didn't have "priceCheck" and "nameCheck"? I am still quite confused even you solved the problem. Why it didn't update my data at the first place? – Sam Jun 23 '22 at 08:15
  • 1
    it's all about array mutation. the problem is that `Array.sort` mutates the array so no updates will be triggered. You can have a dummy `console.log` inside a `useEffect` and you can see that. By adding 2 boolean values, this triggers the update of the component and it rerenders. So these are needed if you need to check the checked/unchecked value for asc/desc but the real problem is the mutation. – Apostolos Jun 23 '22 at 08:24
  • thanks, it's really helpful and easy to understand! – Sam Jun 23 '22 at 08:25
  • i also updated my answer because i focused mainly at where product should be declared which should not be the case in general. :) – Apostolos Jun 23 '22 at 08:27
  • 1
    OK so that was the problem I was trying with the code and the `onChange` was triggering correctly, I could not figure out why it was not working in the original form, any way thanks. – Alan Omar Jun 23 '22 at 08:31
0

This all looks good, I run it on codesandbox and it does rerenders. But anyway I would suggest you to move product array out of the component, and just to use it as initial value for useState hook, and on state update use input parameter from setData callback - eg: setData(prev => [...prev.sort(...)]), no need each time to reference initial array since you already used it when calling hook in your component.

Milos Pavlovic
  • 1,355
  • 1
  • 2
  • 7
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 23 '22 at 20:47