useState updates correctly when adding to array, but not removing

Viewed 323

I have a simple component that allows me to select an item from a list, then remove an item from a list. I display the active list within a parent component. No matter what I do or how I approach it, the removal of an active component is never updated unless they are all in active.

Here is a smaller (yet large snippet) of how it is setup. Below it I describe where I found to be the problem:

const Viewer = () => {
    const [items, setItems] = useState(["inactive"]);

    return (
        <ItemSelect setItems={setItems}  selected={items}/>
        <DisplayItems items={items}/>    
    )
}

const ItemSelect = ({setItems, selected}) => {
    const handleActiveItems = (activeItems) => {
        setItems(activeItems);
    }
    return (
        <SelectItems
            handleActiveItems={handleActiveItems}
            items={selected}
        />
    )
}

const SelectItems = ({handleActiveItems, items}) => {
    const [selected, setSelected] = useState([])

    useEffect(() => {
        setSelected(items);
    }, [items]);

    const randomTestItem = ["apple", "peach", "orange"];

    const handleOnClick = (isSelected, item) => {
        let tmpItems = items;
        if (isSelected) {
            let index = tmpItems.indexOf("inactive");
            if (index > -1) {
                handleActiveItems([option]);
            } else {
                handleActiveItems([...selected, option]);
            }
        } else if (!isSelected) {
            let index = tmpItems.indexOf(option);
            if (index > -1) {
                tmpItems.splice(index, 1);
                if (tmpItems.length === 0) {
                    handleActiveItems(["inactive"]);
                } else {
                    handleActiveItems([tmpItems]);
                }
            }
        }
    }

    return (
        {
            randomTestItem?.map((item,index) => {
                return (
                    <DisplayClickable item={item} onClick={handleOnClick} key={index}/> 
                )
            })
        }
    )
}

<DisplayClickable item={item} onClick={handleOnClick}/> holds a useState() that toggle from active/inactive.

I've tested this in many different area's I believe the crux of the problem to be here:

} else if (!isSelected) {
    let index = tmpItems.indexOf(option);
    if (index > -1) {
        tmpItems.splice(index, 1);
        if (tmpItems.length === 0) {
            handleActiveItems(["inactive"]);
        } else {
            handleActiveItems([tmpItems]);
        }
    }
}

specifically:

} else {
    handleActiveItems([tmpItems]);
}

When I unselect all the items and switch the array back to "inactive", everything updates instantly and exactly how you would expect. Selecting items always adds to the list correctly, it's removing them that everything goes wonky. I've done a console.log right before calling handleActiveItems() and the tmpItems array is always correct to what it should be. It just never updates the set state.

Within handleActiveItems the log also shows it is receiving the array just before setting it. It just never sets it.

2 Answers

I believe since you are using the splice method, you just modify the existing array and React does not recognize it as "updatable". You can try to use the filter method:

    if (index > -1) {
        const newArray = tmpItems.filter((_, itemIndex)=> itemIndex !== index)
        if (newArray.length === 0) {
            handleActiveItems(["inactive"]);
        } else {
            handleActiveItems(newArray);
        }
    }

With the code above, filter method will generate a new array.

Give it a try, hopefully it will help =)

update

I've just realized, maybe you don't need the extra [] you are putting into handleActiveItems(). So instead of:

handleActiveItems([tmpItems])

It could be just:

handleActiveItems(tmpItems)

I figured it out.

It all came down to this line:

let tmpItems = items;

Changing to this:

let tmpItems = [...items];

for some reason allowed React to pay more attention and notice that there was in fact a change.

I just changed in my development build and it works without a hiccup.

Related