当前位置: 首页 > 知识库问答 >
问题:

反应:警告列表中的每个子项都应具有唯一的键[重复]

松和安
2023-03-14

我一直在调试这个,开始掉头发。到目前为止,我还没有找到解决办法。这是摘要组件。我最初是为Home组件编写测试的,但是由于样式化的组件,它出现了一些错误,所以我的技术负责人告诉我为这个新的trister组件(这是Home组件中的新组件)编写测试,因为它可能有一些效果。运行<代码>摘要时。测验tsx我收到此错误(与钥匙相关):

 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>
    </>
  )
}

共有2个答案

郜俊健
2023-03-14

可能与调谐器相关。地图。react键需要位于映射的最外层元素上,在本例中为片段。摘要链接在集合中似乎是唯一的,但如果不是,则可能需要为元素提供唯一的id属性,以用作react键。

{tunesTeasers.map(teaser => {
  return (
    <Fragment key={teaser.link}>
      {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>
      )}
    </Fragment>
  )
})}

萧晔
2023-03-14

这肯定是一个数组键问题,但似乎在每个数据集(数组)中都有唯一的alt属性。

<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>
 类似资料:
  • 得到一个警告:列表中的每个孩子都应该有一个唯一的“键”道具。我想不出我需要用哪把钥匙。 我红了反应指南,但它不能帮助我理解如何在我的情况下使用它

  • 在这个简单的React应用程序中,我不明白为什么我会收到以下警告信息: 警告:列表中的每个孩子都应该有一个唯一的“键”道具。 对我来说,似乎我把密钥放在了正确的位置,形式为key={item.login.uuid} 我怎样才能摆脱警告信息?把钥匙放在哪里合适? 应用程序。js 列表js

  • 向整个社区问好。 我正在做一个项目,在这个项目中,我所在城市的餐馆可以通过这个应用程序放置他们的菜肴并生成订单。 但是我在执行项目时遇到了一个错误。 这就是我犯的错误。 我正在用React本地做我的前端项目。 **我的代码是这个-我的前端** 应用程序。js 餐食 创建一个调用Flatlist操作的列表,并管理列表中的信息。 列表js 获取我的项目的url。 UseFecth.js 我的modal

  • 我对如何创建一个简单的todo应用程序的反应和探索方式还不熟悉。我当前收到错误“警告:列表中的每个孩子都应该有一个唯一的“键”道具。”一切似乎都很好,但我一定是做错了什么。 如果有人能帮上忙,那就br太好了/解释为什么会发生这个错误,那就太好了。

  • 我调用一个APIendpoint,将其数据保存到一个状态,然后呈现它。它显示在浏览器中,但控制台上有一个警告:。 我的: 我不明白在render()中该将关键道具放在哪里。这是我的代码片段: 不使用钥匙道具会带来什么改进?我被这些

  • 我有一个问题,“列表中的每个孩子都应该在我的应用程序中有一个唯一的“键”道具”错误。我可以打印表格,但我不知道为什么它给我这个错误,因为我提供了一个唯一的ID列表中的每个项目。 我也尝试过向我的表头添加一个键属性,但这并不能修复错误。 如有任何意见,将不胜感激 客户组件 表组件