3

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:

jsfiddle

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).

13
  • 2
    Changing the prop selected will reset selectedComp if you put a console log in your useEffect you may find that that is resetting it every time. Commented Oct 4, 2019 at 9:57
  • You're right! Altough I don't know why it is resetting; selected should only update once, when my data is loaded and the default status is set. I guess I'll have to track that further down. Commented Oct 4, 2019 at 10:07
  • But you still solved my issue (I didn't even think about that useEffect line); if you post it as an answer, I'll accept it. Commented Oct 4, 2019 at 10:08
  • 1
    I think selected is an array or object (reference value) not a string (primitive value) and selected is changed by the reducer or whatever addToCart causes to happen. You are right that useEffect would not be called if selected didn't change but in JS {}!=={} meaning that {} does not equal {} because it's a different reference. Commented Oct 4, 2019 at 10:22
  • 1
    You should fix the problem at it's source, I suspect it's something that addToCart does or see where selected comes from <ConfigCheckboxGroup selected={???} Commented Oct 4, 2019 at 10:42

1 Answer 1

4

Changing the prop selected will reset selectedComp if you put a console log in your useEffect you may find that that is resetting it every time.

You need to track down where selected comes from (redux?) and how it's set (addToCart?).

A dirty fix could be to only set selectedComp when component mounts, this is dirty and will/should cause react-hooks/exhaustive-deps lint to trigger:

useEffect(() => {
  setSelectedComp(selected);
}, []);

But better to track down what's going wrong with selected, if it comes from redux then maybe just use selected instead and forget about selectedComp since that is just a copy.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.