Edit:
My error occured because I passed an array as a second parameter to useEffect. Even though the values inside the array stayed the same, the reference changed constantly, therefore useEffect was called constantly and reset my checkbox values. That array was created by an useState call. I replaced useState by useReducer (reducer only changes the object reference if the object is actually changed) and updated some missing dependencies higher up the component tree.
Original question:
I have trouble updating a state in a functional component.
My question is somewhat similiar to this one: React SetState doesn't call render
I'm already copying my state object (by using array.filter) instead of referencing it; but my state still doesn't update.
In order to track down the problem, I tried re-creating the problem in a minimal example:
But in my minimal example, everything works as expected. I'm unable to reproduce the error.
Here is my example where the state doesn't update:
configCheckboxGroup.tsx:
import classNames from "classnames";
import React, { useState, useEffect } from "react";
import { Component } from "../../model";
import CheckboxPanel from "./panels/checkboxPanel";
interface configCheckboxGroupProps {
className?: string;
choices: Array<Component>;
selected: Array<string>;
addToCart: (items: Array<Component>) => void;
}
const ConfigCheckboxGroup: React.SFC<configCheckboxGroupProps> = ({
className,
choices,
selected,
addToCart,
}) => {
const [ selectedComp, setSelectedComp ] = useState<Array<string>>(selected);
// device loads later, selected has to be updated
useEffect(() => {
setSelectedComp(selected);
}, [selected]);
const handleOnChange = (ev: React.FormEvent, id: string) => {
console.debug(id);
console.debug(selectedComp.filter(el => el !== id));
if (selectedComp.includes(id)) {
// was already checked || this line is not working!
setSelectedComp(selectedComp.filter(el => el !== id));
} else {
// was not checked
setSelectedComp([...(selectedComp), id]);
}
const selected = choices.filter(el => selectedComp.includes(el.reference._id));
addToCart(selected);
};
return (
<div className={classNames("panellist", className)}>
{
choices.map(el => {
return (
<CheckboxPanel
image={ el.reference.picture ? el.reference.picture : undefined }
name={ el.reference.name }
id={ el.reference._id }
price={ el.reference.price ? el.reference.price :
el.price ? el.price : 0 }
key={ el._id }
checked={ selectedComp.includes(el.reference._id) }
onChange={ handleOnChange }
/>
)
})
}
<span>
{ selectedComp }
</span>
</div>
)
}
export default ConfigCheckboxGroup;
And checkboxPanel.tsx:
import classNames from "classnames";
import React from "react";
import "./checkboxPanel.scss";
import noImage from "../../../resources/images/errors/no-image.svg";
interface PanelProps {
className?: string;
image?: string;
name: string;
id: string;
price: number;
checked: boolean;
onChange: (ev: React.FormEvent, id: string) => void;
}
const CheckboxPanel: React.SFC<PanelProps> = ({
className,
image,
name,
id,
price,
checked,
onChange,
}) => {
const getImage = () => {
if (image) {
return image;
} else {
return noImage;
}
}
return (
<div className={classNames("panel", "checkbox-panel", className)}>
<div className="top">
<div className="image">
<img alt="Product" src={getImage()} />
</div>
<div className="name">
{name}
</div>
</div>
<div className="bottom">
<div className="category">
{ Number(price).toFixed(2) } €
</div>
<div>
<input type="checkbox"
checked={ checked }
onChange={ (e) => onChange(e, id) }
/>
</div>
</div>
</div>
)
};
export default CheckboxPanel;
The only difference between the examples is that in the second one, I call the handle function inside a child component. But I do the same thing on other occasions as well: I have a very similar Component configRadioGroup with radio buttons instead of checkboxes where everything works fine.
I tried playing around by manually filtering the array and trying a lot of other things, but nothing seemed to help. This is why, as a last try, I ask here (although I know that this question is not a good one due to it being very specific).
selectedwill resetselectedCompif you put a console log in your useEffect you may find that that is resetting it every time.selectedshould only update once, when my data is loaded and the default status is set. I guess I'll have to track that further down.useEffectline); if you post it as an answer, I'll accept it.selectedis an array or object (reference value) not a string (primitive value) andselectedis changed by the reducer or whateveraddToCartcauses to happen. You are right that useEffect would not be called ifselecteddidn't change but in JS{}!=={}meaning that{}does not equal{}because it's a different reference.addToCartdoes or see whereselectedcomes from<ConfigCheckboxGroup selected={???}