Я делаю проект для того, чтобы запустить подрядную компанию и где-то я должен показать все квитанции для каждого пользователя компании, но я получил эту ошибку:
Warning: Each child in a list should have a unique "key" prop.
Хотя я посмотрел код, я не нашел никаких ошибок
Как я могу решить проблему?
Через этот файл я вывожу таблицу, а в этой таблице выводится список чеков
import FuseScrollbars from "@fuse/core/FuseScrollbars";
import _ from "@lodash";
import Checkbox from "@material-ui/core/Checkbox";
import Icon from "@material-ui/core/Icon";
import Table from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TablePagination from "@material-ui/core/TablePagination";
import TableRow from "@material-ui/core/TableRow";
import Typography from "@material-ui/core/Typography";
import clsx from "clsx";
import { motion } from "framer-motion";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { withRouter } from "react-router-dom";
import FuseLoading from "@fuse/core/FuseLoading";
import {
getSalaryScales,
selectSalaryScales,
} from "../store/salaryScalesSlice";
import SalaryScalesTableHead from "./SalaryScalesTableHead";
import Moment from "react-moment";
import IconButton from "@material-ui/core/IconButton";
import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown";
import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp";
function SalaryScalesTable(props) {
const dispatch = useDispatch();
const salaryScales = useSelector(selectSalaryScales);
const searchText = useSelector(
({ salaryScalesApp }) => salaryScalesApp.salaryScales.searchText
);
const [loading, setLoading] = useState(true);
const [selected, setSelected] = useState([]);
const [data, setData] = useState(salaryScales);
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
const [order, setOrder] = useState({
direction: "asc",
id: null,
});
const [open, setOpen] = useState(false);
console.log("order: ", order);
useEffect(() => {
dispatch(getSalaryScales()).then(() => setLoading(false));
}, [dispatch]);
useEffect(() => {
if (searchText.length !== 0) {
setData(
_.filter(salaryScales, (item) =>
item.id.toLowerCase().includes(searchText.toLowerCase())
)
);
setPage(0);
} else {
setData(salaryScales);
}
}, [salaryScales, searchText]);
function handleRequestSort(event, property) {
const id = property;
let direction = "desc";
if (order.id === property && order.direction === "desc") {
direction = "asc";
}
setOrder({
direction,
id,
});
}
function handleSelectAllClick(event) {
if (event.target.checked) {
setSelected(data.map((n) => n.id));
return;
}
setSelected([]);
}
function handleDeselect() {
setSelected([]);
}
function handleClick(item) {
props.history.push(`/apps/salary-scales-section/salary-scales/${item.id}`);
}
function handleCheck(event, id) {
const selectedIndex = selected.indexOf(id);
let newSelected = [];
if (selectedIndex === -1) {
newSelected = newSelected.concat(selected, id);
} else if (selectedIndex === 0) {
newSelected = newSelected.concat(selected.slice(1));
} else if (selectedIndex === selected.length - 1) {
newSelected = newSelected.concat(selected.slice(0, -1));
} else if (selectedIndex > 0) {
newSelected = newSelected.concat(
selected.slice(0, selectedIndex),
selected.slice(selectedIndex + 1)
);
}
setSelected(newSelected);
}
function handleChangePage(event, value) {
setPage(value);
}
function handleChangeRowsPerPage(event) {
setRowsPerPage(event.target.value);
}
if (loading) {
return <FuseLoading />;
}
if (data.length === 0) {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1, transition: { delay: 0.1 } }}
className="flex flex-1 items-center justify-center h-full"
>
<Typography color="textSecondary" variant="h5">
There are no Salary Scales!
</Typography>
</motion.div>
);
}
return (
<div className="w-full flex flex-col">
<FuseScrollbars className="flex-grow overflow-x-auto">
<Table stickyHeader className="min-w-xl" aria-label="collapsible table">
<SalaryScalesTableHead
selectedSalaryScaleIds={selected}
order={order}
onSelectAllClick={handleSelectAllClick}
onRequestSort={handleRequestSort}
rowCount={data.length}
onMenuItemClick={handleDeselect}
/>
<TableBody>
{_.orderBy(
data,
[
(o) => {
switch (order.id) {
case "categories": {
return o.categories[0];
}
default: {
return o[order.id];
}
}
},
],
[order.direction]
)
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((n) => {
const isSelected = selected.indexOf(n.id) !== -1;
return (
<>
<TableRow
className="h-72 cursor-pointer"
hover
role="checkbox"
aria-checked={isSelected}
tabIndex={-1}
key={n.id}
selected={isSelected}
onClick={(event) => handleClick(n)}
>
<TableCell
className="w-40 md:w-64 text-center"
padding="none"
>
<Checkbox
checked={isSelected}
onClick={(event) => event.stopPropagation()}
onChange={(event) => handleCheck(event, n.id)}
/>
</TableCell>
<TableCell>
<IconButton
aria-label="expand row"
size="small"
onClick={() => setOpen(!open)}
className="w-40 md:w-64 text-center"
padding="none"
>
{open ? (
<KeyboardArrowUpIcon />
) : (
<KeyboardArrowDownIcon />
)}
</IconButton>
</TableCell>
<TableCell
className="p-4 md:p-16"
component="th"
scope="row"
align="left"
>
{n.id}
</TableCell>
<TableCell
className="p-4 md:p-16"
component="th"
scope="row"
align="center"
>
<Moment>{n.createdAt}</Moment>
</TableCell>
<TableCell
className="p-4 md:p-16"
component="th"
scope="row"
align="center"
>
{n.isActive ? (
<Icon className="text-green text-20">
check_circle
</Icon>
) : (
<Icon className="text-red text-20">
remove_circle
</Icon>
)}
</TableCell>
</TableRow>
</>
);
})}
</TableBody>
</Table>
</FuseScrollbars>
<TablePagination
className="flex-shrink-0 border-t-1"
component="div"
count={data.length}
rowsPerPage={rowsPerPage}
page={page}
backIconButtonProps={{
"aria-label": "Previous Page",
}}
nextIconButtonProps={{
"aria-label": "Next Page",
}}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</div>
);
}
export default withRouter(SalaryScalesTable);
Этот пост, на который вы ссылаетесь, мне не полезен
Рассматривали ли вы возможность взглянуть на документация React, который объясняет это?
каждая функция .map() для дочернего элемента требует ключевого атрибута
Здесь вы должны дать key
первому div:
return (
<div key={n.id}>
<TableRow
className="h-72 cursor-pointer"
hover
role="checkbox"
aria-checked={isSelected}
tabIndex={-1}
selected={isSelected}
onClick={(event) => handleClick(n)}
>
<TableCell
className="w-40 md:w-64 text-center"
padding="none"
>
<Checkbox
checked={isSelected}
onClick={(event) => event.stopPropagation()}
onChange={(event) => handleCheck(event, n.id)}
/>
</TableCell>
<TableCell>
<IconButton
aria-label="expand row"
size="small"
onClick={() => setOpen(!open)}
className="w-40 md:w-64 text-center"
padding="none"
>
{open ? (
<KeyboardArrowUpIcon />
) : (
<KeyboardArrowDownIcon />
)}
</IconButton>
</TableCell>
<TableCell
className="p-4 md:p-16"
component="th"
scope="row"
align="left"
>
{n.id}
</TableCell>
<TableCell
className="p-4 md:p-16"
component="th"
scope="row"
align="center"
>
<Moment>{n.createdAt}</Moment>
</TableCell>
<TableCell
className="p-4 md:p-16"
component="th"
scope="row"
align="center"
>
{n.isActive ? (
<Icon className="text-green text-20">
check_circle
</Icon>
) : (
<Icon className="text-red text-20">
remove_circle
</Icon>
)}
</TableCell>
</TableRow>
</div>
);
Ваша проблема от <><TableRow key={n.id}></TableRow></>
В вашем случае, я думаю, вам не нужно иметь <></>
, так что вы можете избавиться от этого, ваша проблема с отсутствующим ключом будет решена.
return (
<TableRow
className="h-72 cursor-pointer"
hover
role="checkbox"
aria-checked={isSelected}
tabIndex={-1}
key={n.id}
selected={isSelected}
onClick={(event) => handleClick(n)}
>
<TableCell
className="w-40 md:w-64 text-center"
padding="none"
>
<Checkbox
checked={isSelected}
onClick={(event) => event.stopPropagation()}
onChange={(event) => handleCheck(event, n.id)}
/>
</TableCell>
<TableCell>
<IconButton
aria-label="expand row"
size="small"
onClick={() => setOpen(!open)}
className="w-40 md:w-64 text-center"
padding="none"
>
{open ? (
<KeyboardArrowUpIcon />
) : (
<KeyboardArrowDownIcon />
)}
</IconButton>
</TableCell>
<TableCell
className="p-4 md:p-16"
component="th"
scope="row"
align="left"
>
{n.id}
</TableCell>
<TableCell
className="p-4 md:p-16"
component="th"
scope="row"
align="center"
>
<Moment>{n.createdAt}</Moment>
</TableCell>
<TableCell
className="p-4 md:p-16"
component="th"
scope="row"
align="center"
>
{n.isActive ? (
<Icon className="text-green text-20">
check_circle
</Icon>
) : (
<Icon className="text-red text-20">
remove_circle
</Icon>
)}
</TableCell>
</TableRow>
Если вы хотите сохранить <></>
(как сокращение для <React.Fragment></React.Fragment>
). Вам нужно явно вызвать
<React.Fragment key={n.id}>
<TableRow></TableRow>
</React.Fragment>
Список должен иметь уникальный ключ при отображении. Лучший способ выбрать ключ — использовать строку, которая однозначно идентифицирует элемент списка среди его братьев и сестер. Чаще всего вы будете использовать идентификаторы из ваших данных в качестве ключей.
YourArray
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((n) => {
const isSelected = selected.indexOf(n.id) !== -1;
return (
<React.Fragment key={n.id}>
<TableRow
className="h-72 cursor-pointer"
hover
role="checkbox"
aria-checked={isSelected}
tabIndex={-1}
key={n.id}
selected={isSelected}
onClick={(event) => handleClick(n)}
>
<TableCell
className="w-40 md:w-64 text-center"
padding="none"
>
<Checkbox
checked={isSelected}
onClick={(event) => event.stopPropagation()}
onChange={(event) => handleCheck(event, n.id)}
/>
</TableCell>
<TableCell>
<IconButton
aria-label="expand row"
size="small"
onClick={() => setOpen(!open)}
className="w-40 md:w-64 text-center"
padding="none"
>
{open ? (
<KeyboardArrowUpIcon />
) : (
<KeyboardArrowDownIcon />
)}
</IconButton>
</TableCell>
<TableCell
className="p-4 md:p-16"
component="th"
scope="row"
align="left"
>
{n.id}
</TableCell>
<TableCell
className="p-4 md:p-16"
component="th"
scope="row"
align="center"
>
<Moment>{n.createdAt}</Moment>
</TableCell>
<TableCell
className="p-4 md:p-16"
component="th"
scope="row"
align="center"
>
{n.isActive ? (
<Icon className="text-green text-20">
check_circle
</Icon>
) : (
<Icon className="text-red text-20">
remove_circle
</Icon>
)}
</TableCell>
</TableRow>
</React.Fragment>
);
})
Отвечает ли это на ваш вопрос? Предупреждение Reactjs: каждый дочерний элемент в списке должен иметь уникальную «ключевую» опору.