Home > Back-end >  Helper class for react hooks, context won't render after new value is return from helper
Helper class for react hooks, context won't render after new value is return from helper

Time:10-18

I have hook component with a useContext and useState, both works as intended. I also have a useEffect to trigger on any change of my state "selectedItems". I also have a utility class with some simple logic that will return a modified array. It looks like this:

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

and

const ItemBox = ({item}) => {
const [selectedItems, setSelectedItems] = useContext(SelectedContext);  return (
  <div onClick={() => setSelectedItems(InventoryUtil.selectOrDeselectItem(item, selectedItems))} className="itemBox">
  </div>)};

And helper/util function looks like this:

    selectOrDeselectItem(item, selectedItems) {
    if (selectedItems.includes(item)) {
        selectedItems.splice(selectedItems.indexOf(item), 1);
        return selectedItems;
    }
    selectedItems.push(item);
    return selectedItems;
}

And the logic works when I'm logging it to the console and everything looks fine, but my useEffect is not triggering when selectedItems is changing via my helper function. It only triggers when i feed a direct value into it, but everything that goes via my util/helper class won't trigger it.

So my question is why and what is the best practice when it comes to creating helper/utils logic functions with react hooks that simply takes in some value and return a modified array.

CodePudding user response:

This is because you are returning the reference to the same object and React cannot detect it as an updated state variable. Also the best practice is to use functional setState pattern when the next value depends on the previous value.

You can use this, it solves both:

onClick={() => setSelectedItems((selectedItems) => InventoryUtil.selectOrDeselectItem(item, [...selectedItems])))}

Note that the best practice would be to not modify any incoming components so instead of destructuring while passing, you could do it at the top of your function:

selectOrDeselectItem(item, initSelectedItems) {
const selectedItems = [...initSelectedItems];
    if (selectedItems.includes(item)) {
        selectedItems.splice(selectedItems.indexOf(item), 1);
        return selectedItems;
    }
    selectedItems.push(item);
    return selectedItems;
}

and keep this like:

onClick={() => setSelectedItems((selectedItems) => InventoryUtil.selectOrDeselectItem(item, selectedItems)))}

CodePudding user response:

You function modifies the original array instead of creating a new one

useEffect check if the variable changes no it's content so it checks references:

test(1, 1)
test([], [])


function test(a, b) {
  console.log('a'   '==='   'b', a === b)
}

Your function must be pure

selectOrDeselectItem(item, selectedItems) {
    if (selectedItems.includes(item)) {
        return selectedItems.filter(i => i !== item);
    }
    return [...selectedItems, item];
}
  • Related