反应:警告列表中的每个孩子都应该有一个唯一的钥匙 [英] React: Warning each child in a list should have a unique key
问题描述
我一直在调试并开始脱发.到目前为止,我还没有找到解决方案.这是Teaser
组件.我最初是为Home
组件编写测试,但由于组件的样式而有一些错误,所以我的技术负责人告诉我要为此新的Teaser
组件(这是Home
组件中的新组件)编写测试. ),因为它可能正在发挥作用.运行Teaser.test.tsx
时,出现此错误(与键有关):
I have been debugging this and started losing hair. So far I haven't found a solution yet. This the Teaser
component. I was initally writing tests for Home
component, but it had some errors because of styled-components, so my techlead told me to write tests for this new Teaser
component (which is the component that was new in Home
component), because maybe it is having some effect. When running the Teaser.test.tsx
I get this error (related to keys):
FAIL src/features/home/Teaser.test.tsx (6.781s)
Teaser component
× renders Teaser component when user has tonieboxes (185ms)
● Teaser component › renders Teaser component when user has tonieboxes
expect(jest.fn()).not.toBeCalled()
Expected number of calls: 0
Received number of calls: 1
1: "Warning: Each child in a list should have a unique \"key\" prop, "·
Check the render method of `Teaser`.", "", "
in Fragment (created by Teaser)
in Teaser (at Teaser.test.tsx:16)
in I18nextProvider (at test-utils/index.jsx:38)
in AuthProvider (at test-utils/index.jsx:37)
in ConfigProvider (at test-utils/index.jsx:36)
in ThemeProvider (at test-utils/index.jsx:34)
in Router (created by MemoryRouter)
in MemoryRouter (at test-utils/index.jsx:33)
in Providers"
41 | // eslint-disable-next-line jest/no-duplicate-hooks
42 | afterEach(() => {
> 43 | expect(console.error).not.toBeCalled()
| ^
44 | expect(console.warn).not.toBeCalled()
45 |
46 | // Reset any request handlers that we may add during the tests,
at Object.<anonymous> (src/setupTests.js:43:29)
我的预告片测试:
import React from 'react'
import { render, screen } from '../../utils/test-utils'
import { Teaser, Tonieboxes } from './Teaser'
const tonieboxes: Tonieboxes[] = [
{
id: 'toniebox-id-1',
name: 'toniebox-name-1',
imageUrl: 'toniebox-image-1',
},
]
describe('Teaser component', () => {
const welcomeMessage = 'welcome-message'
test('renders Teaser component when user has tonieboxes', () => {
render(<Teaser tonieboxes={tonieboxes} />)
expect(screen.getByTestId(welcomeMessage)).toBeInTheDocument()
})
})
我的预告片组件:
import React, { useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import {
variables,
Text,
Bello,
media,
Headline,
Modal,
} from '@boxine/tonies-ui'
import { Link } from 'react-router-dom'
import { HorizontalScrollList } from '../../components/HorizontalScrollList/index'
import BenjaminBlümchen from '../../assets/01_Teaser_Charakter Benjamin.png'
import BibiAndTinaImg from '../../assets/05_Teaser_Charaktere Bibi&Tina.png'
/* German Images */
import newEpisodesImgDE from '../../assets/03_Teaser_Welcome Audiothek DE.png'
import newTonieBoxTurqouiseImgDE from '../../assets/02_2_Teaser_Toniebox Turquoise DE.png'
import creativeToniesImgDE from '../../assets/04_Teaser_Kreativ-Tonies DE.png'
import registerTonieboxImgDE from '../../assets/02_1_Teaser_Toniebox registrieren DE.png'
/* English Images */
import newEpisodesImg from '../../assets/03_Teaser_Welcome Audiothek EN.png'
import newTonieBoxTurqouiseImg from '../../assets/02_2_Teaser_Toniebox Turquoise EN.png'
import creativeToniesImg from '../../assets/04_Teaser_Kreativ-Tonies EN.png'
import AddTonieboxModalContent from '../tonieboxes-page/components/AddTonieboxModalContent'
import registerTonieboxImg from '../../assets/02_1_Teaser_Toniebox registrieren EN.png'
export interface Tonieboxes {
id: string
name: string
imageUrl: string
}
interface TeaserProps {
tonieboxes: Tonieboxes[]
}
interface TunesTeaser {
alt: string
src: string
link: string
noTonieboxes?: boolean
}
const tunesTeasersDE: TunesTeaser[] = [
{
alt: 'BenjaminBlümchen',
src: BenjaminBlümchen,
link: '/audio-library?filter=beee313f-55b2-40c1-8032-c41057f92e21',
},
{
alt: 'Tonieboxen',
src: newTonieBoxTurqouiseImgDE,
link: '/tonieboxes',
},
{
alt: '400 Neue Folgen',
src: newEpisodesImgDE,
link: '/audio-library',
},
{
alt: 'Kreativ Tonies',
src: creativeToniesImgDE,
link: '/creative-tonies',
},
{
alt: 'Bibi und Tina',
src: BibiAndTinaImg,
link: '/audio-library?filter=dacc4edb-ad1d-4ecd-b98c-b4b31983b5f8',
},
]
const tunesTeasersNoTonieboxesDE: TunesTeaser[] = [
{
alt: 'BenjaminBlümchen',
src: BenjaminBlümchen,
link: '/audio-library?filter=beee313f-55b2-40c1-8032-c41057f92e21',
},
{
alt: 'Registriere Deine Toniebox',
src: registerTonieboxImgDE,
link: '',
noTonieboxes: true,
},
{
alt: '400 Neue Folgen',
src: newEpisodesImgDE,
link: '/audio-library',
},
{
alt: 'Kreativ Tonies',
src: creativeToniesImgDE,
link: '/creative-tonies',
},
{
alt: 'Bibi und Tina',
src: BibiAndTinaImg,
link: '/audio-library?filter=dacc4edb-ad1d-4ecd-b98c-b4b31983b5f8',
},
]
const tunesTeasersEng: TunesTeaser[] = [
{
alt: 'Benjamin Bluemchen',
src: BenjaminBlümchen,
link: '/audio-library?filter=beee313f-55b2-40c1-8032-c41057f92e21',
},
{
alt: 'Tonieboxes',
src: newTonieBoxTurqouiseImg,
link: '/tonieboxes',
},
{
alt: '400 New Episodes',
src: newEpisodesImg,
link: '/audio-library',
},
{
alt: 'Creative Tonies',
src: creativeToniesImg,
link: '/creative-tonies',
},
{
alt: 'Bibi and Tina',
src: BibiAndTinaImg,
link: '/audio-library?filter=dacc4edb-ad1d-4ecd-b98c-b4b31983b5f8',
},
]
const tunesTeasersNoTonieboxesEng: TunesTeaser[] = [
{
alt: 'Benjamin Bluemchen',
src: BenjaminBlümchen,
link: '/audio-library?filter=beee313f-55b2-40c1-8032-c41057f92e21',
},
{
alt: 'Register Your Toniebox',
src: registerTonieboxImg,
link: '',
noTonieboxes: true,
},
{
alt: '400 New Episodes',
src: newEpisodesImg,
link: '/audio-library',
},
{
alt: 'Creative Tonies',
src: creativeToniesImg,
link: '/creative-tonies',
},
{
alt: 'Bibi and Tina',
src: BibiAndTinaImg,
link: '/audio-library?filter=dacc4edb-ad1d-4ecd-b98c-b4b31983b5f8',
},
]
const Wrapper = styled.div`
margin: 1rem 0 0;
`
const StyledLink = styled(Link)`
display: block;
`
const List = styled.li`
display: block;
cursor: pointer;
`
const StyledHeadline = styled(Headline)`
text-align: center;
`
const StyledText = styled(Text)`
text-align: center;
${media.tablet`
font-size: 1rem;
`}
${media.laptop`
font-size: 1.25rem;
`}
`
const TextWrapper = styled.div`
position: relative;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 1rem;
width: 18rem;
${media.mobileL`
width: 21rem;
`}
${media.tablet`
width: 26rem;
`}
${media.laptop`
width: 27rem;
`}
`
const StyledHorizontalScrollList = styled(HorizontalScrollList)`
ul {
padding: 0 1rem 0.5rem 0;
}
`
const ScrollListWrapper = styled.div`
margin-left: 1rem;
${media.laptopL`
margin-left: 0;
`}
`
export const TeaserCard = styled.img`
width: 100%;
height: 100%;
border-radius: 1rem;
box-shadow: 0.25rem 0.25rem 0 0 ${props => props.theme.DirtyWhiteDarker};
${media.tablet`
box-shadow: 0.375rem 0.375rem 0 0 ${props => props.theme.DirtyWhiteDarker};
`}
${media.laptop`
box-shadow: 0.5rem 0.5rem 0 0 ${props => props.theme.DirtyWhiteDarker};
`}
`
export function Teaser({ tonieboxes }: TeaserProps) {
const [columns, setColumns] = useState(3)
const [toggleTonieboxModal, setToggleTonieboxModal] = useState(false)
const [allBoxes, setAllBoxes] = useState<Tonieboxes[]>(tonieboxes)
const [tunesTeasers, setTunesTeasers] = useState<TunesTeaser[]>([])
const { i18n } = useTranslation()
const { t } = useTranslation(['home'])
function toggleModal() {
setToggleTonieboxModal(!toggleTonieboxModal)
}
function tonieboxAdded(toniebox) {
setAllBoxes([...allBoxes, toniebox])
}
useEffect(() => {
function update() {
const matchTablet = window.matchMedia(
`(min-width: ${variables.screenTablet}px)`
).matches
const matchScreenMobileLarge = window.matchMedia(
`(min-width: ${variables.screenMobileL}px)`
).matches
setColumns(matchTablet ? 2.75 : matchScreenMobileLarge ? 2 : 1.35)
}
update()
function checkAndSetTunesTeasers() {
if (i18n.language === 'de') {
if (tonieboxes.length === 0) {
setTunesTeasers(tunesTeasersNoTonieboxesDE)
} else {
setTunesTeasers(tunesTeasersDE)
}
} else {
if (tonieboxes.length === 0) {
setTunesTeasers(tunesTeasersNoTonieboxesEng)
} else {
setTunesTeasers(tunesTeasersEng)
}
}
}
checkAndSetTunesTeasers()
window.addEventListener('resize', update)
return () => window.removeEventListener('resize', update)
}, [i18n.language, tonieboxes.length])
return (
<>
<Wrapper>
<TextWrapper>
<StyledHeadline
styleTag={columns === 1.25 ? 'h3' : 'h2'}
dataTestId="welcome-message"
>
Werde ein <Bello>Ipsum</Bello> der Tonies
</StyledHeadline>
<StyledText>
Bist du bereit für Hörabenteuer? Entdecke jetzt die ganze Vielfalt
der Tonies.
</StyledText>
</TextWrapper>
<ScrollListWrapper>
<StyledHorizontalScrollList columns={columns}>
{tunesTeasers.map(teaser => {
return (
<>
{teaser.noTonieboxes ? (
<List key={teaser.alt} onClick={toggleModal}>
<TeaserCard src={teaser.src} alt={teaser.alt} />
</List>
) : (
<StyledLink key={teaser.alt} to={teaser.link}>
<TeaserCard src={teaser.src} alt={teaser.alt} />
</StyledLink>
)}
</>
)
})}
</StyledHorizontalScrollList>
</ScrollListWrapper>
</Wrapper>
<Modal
headline={t('add-toniebox-modal:AddTonieboxModalTitle')}
isOpen={toggleTonieboxModal}
onClose={toggleModal}
>
<AddTonieboxModalContent
onClose={toggleModal}
onSuccess={tonieboxAdded}
/>
</Modal>
</>
)
}
推荐答案
这绝对是一个数组键问题,但似乎每个数据集(数组)中都有唯一的alt
属性.
It's definitely an array key issue, but it seems you have unique alt
attributes in each data set (array).
<StyledHorizontalScrollList columns={columns}>
{tunesTeasers.map(teaser => teaser.noTonieboxes ? (
<List key={teaser.alt} onClick={toggleModal}>
<TeaserCard alt={teaser.alt} src={teaser.src} />
</List>
) : (
<StyledLink key={teaser.alt} to={teaser.link}>
<TeaserCard alt={teaser.alt} src={teaser.src} />
</StyledLink>
)}
</StyledHorizontalScrollList>
这篇关于反应:警告列表中的每个孩子都应该有一个唯一的钥匙的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!