【问题标题】:React: useState filter array not updating state反应:useState 过滤器数组不更新状态
【发布时间】:2020-02-02 15:22:16
【问题描述】:

编辑:

我的错误发生是因为我将一个数组作为第二个参数传递给useEffect。即使数组中的值保持不变,引用也会不断变化,因此useEffect 会不断被调用并重置我的复选框值。该数组是由useState 调用创建的。我将useState 替换为useReducer(reducer 仅在对象实际更改时才更改对象引用)并更新了组件树上一些缺少的依赖项。

原问题:

我在更新功能组件中的状态时遇到问题。

我的问题和这个有点相似: React SetState doesn't call render

我已经在复制我的状态对象(通过使用 array.filter)而不是引用它;但我的状态仍然没有更新。

为了追查问题,我尝试在一个最小示例中重新创建问题:

jsfiddle

但在我的最小示例中,一切都按预期工作。我无法重现错误。

这是状态不更新的示例:

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;

还有 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;

示例之间的唯一区别是,在第二个示例中,我在子组件中调用了句柄函数。但我在其他场合也做同样的事情:我有一个非常相似的组件 configRadioGroup,带有单选按钮而不是复选框,一切正常。

我尝试通过手动过滤数组并尝试许多其他方法来玩弄,但似乎没有任何帮助。这就是为什么,作为最后一次尝试,我在这里问(虽然我知道这个问题不是一个好问题,因为它非常具体)。

【问题讨论】:

  • 如果你在 useEffect 中添加控制台日志,更改 prop selected 将重置 selectedComp,你可能会发现每次都会重置它。
  • 你是对的!尽管我不知道它为什么要重置; selected 应该只更新一次,当我的数据被加载并设置默认状态时。我想我必须进一步追踪。
  • 但是你还是解决了我的问题(我什至没有想到useEffect 行);如果您将其发布为答案,我会接受。
  • 我认为selected 是一个数组或对象(参考值)而不是字符串(原始值),selected 被reducer 或任何addToCart 导致发生的改变。你是对的,如果selected 没有改变,则不会调用useEffect,但在JS 中{}!=={} 意味着{} 不等于{},因为它是一个不同的引用。
  • 你应该从源头上解决问题,我怀疑这是addToCart 所做的事情,或者看看selected 的来源&lt;ConfigCheckboxGroup selected={???}

标签: reactjs typescript react-hooks


【解决方案1】:

如果您在 useEffect 中添加控制台日志,更改 selected 属性将重置 selectedComp,您可能会发现每次都会重置它。

您需要跟踪 selected 来自哪里(redux?)以及它是如何设置的(addToCart?)。

一个肮脏的修复可能是只在组件安装时设置selectedComp,这是肮脏的并且将/应该导致触发react-hooks/exhaustive-deps lint:

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

但最好找出selected 出了什么问题,如果它来自redux,那么可能只使用selected 而忘记selectedComp,因为那只是一个副本。

【讨论】:

    猜你喜欢
    • 2023-04-02
    • 2021-01-14
    • 2021-06-03
    • 1970-01-01
    • 2020-04-19
    • 1970-01-01
    • 2020-03-29
    • 1970-01-01
    • 2021-10-22
    相关资源
    最近更新 更多