簡體   English   中英

當祖父狀態更新時,React 列表元素在孫組件中消失。 警告:列表中的每個孩子都應該有一個唯一的“鍵”

[英]React list element disappears in grandchild component when grandparent state is updated. Warning: Each child in a list should have a unique "key"

我創建了一個屬於側邊欄的功能組件。 它包含 3 個組件。

ProductFilters - main list component, fetches possible data filters from the server, provides a filters state which can be updated by child components when a filter is selected.

FilterOptions - 過濾器類別

選項- 代表過濾器菜單中的每個選項。 當被選中時,它會調用ProductFilters回調來更新過濾器列表。 選擇它后, filters映射正在更新,並且...所選的Option元素消失。 選擇空的地方后,react throws 和 error

index.js:1 警告:列表中的每個子項都應該有一個唯一的“鍵”屬性。 檢查FilterOptions的渲染方法

每個Option元素都有它自己的唯一鍵,即選項名稱。 可能是什么問題?

這是我的代碼:

import axios from "axios";
import List from "@mui/material/List";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import Collapse from "@mui/material/Collapse";
import InboxIcon from "@mui/icons-material/MoveToInbox";
import ExpandLess from "@mui/icons-material/ExpandLess";
import ExpandMore from "@mui/icons-material/ExpandMore";
import Button from "@mui/material/Button";

const Option = (props) => {
    const [selected, setSelected] = useState(false);
    const handleClick = () => {
        setSelected(!selected);
        props.setFilter(props.category, props.name, !selected);
    };

    return (
        <ListItemButton
            selected={selected}
            sx={{ pl: 8 }}
            onClick={handleClick}
        >
            <ListItemText primary={props.name} />
        </ListItemButton>
    );
};

const FilterOptions = (props) => {
    const [open, setOpen] = useState(false);
    const handleClick = () => {
        setOpen(!open);
    };

    const displayOptions = () => {
        const options = [];
        for (const [key, option] of props.filter) {
            options.push(
                <Option
                    key={key}
                    name={option.name}
                    category={props.name}
                    setFilter={props.setFilter}
                />
            );
        }
        return options;
    };
    return (
        <Fragment>
            <ListItemButton sx={{ pl: 4, fontSize: 12 }} onClick={handleClick}>
                <ListItemText primary={props.name} />
            </ListItemButton>
            <Collapse in={open} timeout="auto" unmountOnExit>
                <List component="div" disablePadding>
                    {displayOptions()}
                </List>
            </Collapse>
        </Fragment>
    );
};

export default function ProductFilters(props) {
    const [open, setOpen] = useState(false);
    const [filters, setFilters] = useState(new Map());

    const setFilter = (category, filter, selected) => {
        const options = filters.get(category).set(filter, selected);
        setFilters(new Map(filters.set(category, options)));
    };

    useEffect(() => {
        const fetchFilters = async () => {
            try {
                const res = await axios.get(
                    `${process.env.REACT_APP_API_URL}/api/product/filters`
                );
                const outputFilters = new Map();
                res.data.map((filter) => {
                    const options = new Map();
                    filter.taxonomydata_set.map((option) => {
                        options.set(option.name, {
                            name: option.name,
                            selected: false,
                        });
                    });
                    outputFilters.set(filter.category, options);
                });
                setFilters(outputFilters);
            } catch (err) {}
        };

        fetchFilters();
    }, []);

    const handleClick = () => {
        setOpen(!open);
    };

    const displayFilters = () => {
        let filterList = [];

        for (const [categoryName, filterCat] of filters) {
            filterList.push(
                <FilterOptions
                    key={categoryName}
                    name={categoryName}
                    filter={filterCat}
                    setFilter={setFilter}
                />
            );
        }

        return filterList;
    };

    const handleApplyFilters = () => {
        console.log(filters);
    };

    return (
        <List
            sx={{
                width: "100%",
                maxWidth: 260,
                bgcolor: "background.paper",
            }}
            component="nav"
            aria-labelledby="nested-list-subheader"
        >
            <ListItemButton onClick={handleClick}>
                <ListItemIcon>
                    <InboxIcon />
                </ListItemIcon>
                <ListItemText primary={props.ListTitle} />
                {open ? <ExpandLess /> : <ExpandMore />}
            </ListItemButton>
            <Collapse in={open} timeout="auto" unmountOnExit>
                <List>{displayFilters()}</List>
            </Collapse>
            <Button variant="contained" onClick={handleApplyFilters}>
                Apply filters
            </Button>
        </List>
    );
}

鏈接到 codesanbox 上的代碼

我認為您應該將“密鑰”應用於 Fragment

const FilterOptions = (props) => {
    ....
    return (
        <Fragment key={props.name}>
            ...
        </Fragment>
    );
}

問題解決了。 問題不在於鍵,它們設置正確。 問題出在處理程序函數中。 我已將地圖元素類型從 JSON 覆蓋為布爾值( selected ),這導致我的Option組件在重新渲染期間不再使用 props.name 鍵控,因為名稱已被刪除並被布爾變量替換。

從:

    const setFilter = (category, filter, selected) => {
        const options = filters.get(category).set(filter, selected);
        setFilters(new Map(filters.set(category, options)));
    }; 

變成

    const setFilter = (category, filter, selected) => {
        const option = filters.get(category).get(filter);
        option.selected = selected;
        const options = filters.get(category).set(filter, option);
        setFilters(new Map(filters.set(category, options)));
    }; 

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM