繁体   English   中英

警告:列表中的每个孩子都应该有一个唯一的“关键”道具。 mapStateToProps

[英]Warning: Each child in a list should have a unique "key" prop. mapStateToProps

我是新手,我收到以下警告:

Warning: Each child in a list should have a unique "key" prop.

Check the render method of `TravelerStoriesPage`.

这是我的 TravelerStoriesPage:

const TravelerStoriesPage = ({
  data,
  dispatch,
  currentPage,
  totalPages,
  loading,
  location,
  error,
  scrollingDisabled,
  intl,
}) => {
  const urlParams = new URLSearchParams(location.search);
  const tag = urlParams.get('tag');

  let firstPost = null;
  let secondRow = null;
  let _3x3Grid = null;

  const getImageLinkObject = data => ({
    imageAltText: 'TravelerStories Post Image',
    imageUrl: data.data.image.url,
    linkProps: {
      type: 'NamedLink',
      name: 'TravelerStoriesPage',
      to: {
        pathname: `/stories/${data.uid}`,
      },
    },
    text: (
      <>
        <HeadLine post={data} type="stories">
          <RichText render={data.data.title} />
          &nbsp;
          <RichText render={data.data.sub_title} />
        </HeadLine>
        <br />
        <TagList post={data} />
      </>
    ),
  });

  if (data.length) {
    // First main post of the page
    firstPost = {
      linksPerRow: 1,
      links: [getImageLinkObject(data[0])],
    };
    if (data.length > 1) {
      // Second row of the page (first image)
      secondRow = {
        linksPerRow: 2,
        links: [getImageLinkObject(data[1])],
      };
    }
    // Second row of the page (second image)
    if (data.length > 2) {
      secondRow.links[1] = getImageLinkObject(data[2]);
    }
    // remaining 3 x 3 grid
    if (data.length > 3) {
      _3x3Grid = {
        linksPerRow: 3,
        links: data.slice(3).map(item => getImageLinkObject(item)),  //////////////////USE OF MAP
      };
    }
  }

  useEffect(() => {
    fetchTravelerStoriesPosts(dispatch, {
      pageSize,
      page: 1,
      tag,
    });
    return () => {
      dispatch(resetState());
    };
  }, [tag, dispatch]);

  const fetchError = error ? (
    <p className={css.error}>
      <FormattedMessage id="BlogPage.fetchFailure" />
    </p>
  ) : null;
  const noResult =
    !loading && !error && !data.length ? (
      <p>
        <FormattedMessage id="BlogPage.emptyResponse" />
      </p>
    ) : null;

  return (
    <Page
      scrollingDisabled={scrollingDisabled}
      title={intl.formatMessage({ id: 'TravelerStoriesPage.title' })}
      title={intl.formatMessage({ id: 'TravelerStoriesPage.description' })}
    >
      <LayoutSingleColumn>
        <LayoutWrapperTopbar>
          <TopbarContainer />
        </LayoutWrapperTopbar>

        <LayoutWrapperMain>
          <div className={css.blogContainer}>
            {fetchError}
            {noResult}
            {firstPost && <BlogThumbnailLinks {...firstPost} />}
            {secondRow && <BlogThumbnailLinks {...secondRow} />}
            {_3x3Grid && <BlogThumbnailLinks {..._3x3Grid} />}
            {currentPage !== totalPages && (
              <Button
                rootClassName={css.loadMoreButton}
                {...{
                  inProgress: loading,
                  disabled: false,
                  ready: false,
                }}
                onClick={() => {
                  if (loading) return;
                  fetchTravelerStoriesPosts(dispatch, {
                    tag,
                    pageSize,
                    page: noResult ? currentPage : currentPage + 1,
                  });
                }}
              >
                {noResult ? (
                  <FormattedMessage id="BlogPage.refersh" />
                ) : (
                  <FormattedMessage id="BlogPage.loadMore" />
                )}
              </Button>
            )}

            {!loading && (
              <div className={css.getInContactContainer}>
                <h1 className={css.getInContactTitle}>
                  <FormattedMessage id="TravelerStoriesPage.getInContactTitle" />
                </h1>
                {replaceNewLines(
                  intl.formatMessage({ id: 'TravelerStoriesPage.getInContactDescription' }),
                  { linkify: true }
                )}
                <a className={css.buttonLink} href={'mailto:stories@abcd.com'}>
                  <FormattedMessage id="TravelerStoriesPage.storiesButton" />
                </a>
              </div>
            )}
          </div>
        </LayoutWrapperMain>

        <LayoutWrapperFooter>
          <Footer />
        </LayoutWrapperFooter>
      </LayoutSingleColumn>
    </Page>
  );
};

TravelerStoriesPage.defaultProps = {
  ...initialState,
};

const { bool, func, object, array, number } = PropTypes;

TravelerStoriesPage.propTypes = {
  loading: bool.isRequired,
  error: object,
  totalPages: number,
  currentPage: number,
  data: array.isRequired,
  dispatch: func.isRequired,
  // from injectIntl
  intl: intlShape.isRequired,
};

const mapStateToProps = state => {
  const { data, totalPages, currentPage, loading, error } = state.TravelerStoriesPage;
  return {
    data,
    error,
    loading,
    totalPages,
    currentPage,
    scrollingDisabled: isScrollingDisabled(state),
  };
};

const mapDispatchToProps = dispatch => ({
  dispatch,
});

const TravelerStoriesPageA = compose(
  connect(mapStateToProps, mapDispatchToProps),
  injectIntl
)(TravelerStoriesPage);

export default TravelerStoriesPageA;

博客缩略图链接

const BlogThumbnailLinks = props => {
  const {
    rootClassName,
    className,
    linksPerRow,
    links,
    heading,
    subHeading,
    headingRootClassName,
    subHeadingRootClassName,
    linkClassName,
    linkRootClassName,
    imageWrapperClassName,
  } = props;
  const classes = classNames(rootClassName || css.root, className);
  const headingClasses = headingRootClassName || css.heading;
  const subHeadingClasses = subHeadingRootClassName || css.subHeading;
  return (
    <div className={classes}>
      {heading ? <h2 className={headingClasses}>{heading}</h2> : null}
      {subHeading ? <p className={subHeadingClasses}>{subHeading}</p> : null}
      <div className={css.links}>
        {links.map((link, i) => (
          <ThumbnailLink
            key={`${link.imageUrl}${i}`}
            linksPerRow={linksPerRow}
            linkRootClassName={linkRootClassName}
            className={linkClassName}
            imageWrapperClassName={imageWrapperClassName}
            {...link}
          />
        ))}
      </div>
    </div>
  );
};

BlogThumbnailLinks.defaultProps = {
  rootClassName: null,
  className: null,
  heading: null,
  subHeading: null,
  headingRootClassName: null,
  subHeadingRootClassName: null,
  imageWrapperClassName: null,
};

const namedLinkShape = shape({
  type: oneOf(['NamedLink']).isRequired,
  name: string.isRequired,
  params: object,
  to: shape({
    search: string,
    hash: string,
  }),
});

const externalLinkShape = shape({
  type: oneOf(['ExternalLink']).isRequired,
  href: string.isRequired,
});

BlogThumbnailLinks.propTypes = {
  rootClassName: string,
  className: string,

  linksPerRow: oneOf([1, 2, 3]).isRequired,
  links: arrayOf(
    shape({
      imageUrl: string.isRequired,
      imageAltText: string.isRequired,
      linkProps: oneOfType([namedLinkShape, externalLinkShape]),
      text: node.isRequired,
    })
  ).isRequired,

  heading: node,
  subHeading: node,
  headingRootClassName: string,
  subHeadingRootClassName: string,
  linkClassName: string,
  linkRootClassName: string,
  imageWrapperClassName: string,
};

export default BlogThumbnailLinks;

缩略图链接

const ThumbnailLink = props => {
  const {
    className,
    rootClassName,
    imageWrapperClassName,
    linksPerRow,
    imageUrl,
    imageAltText,
    linkProps,
    text,
  } = props;
  const { type, name, params, to, href } = linkProps || {};
  const classes = classNames(rootClassName || css.link, className, {
    [css.link1Columns]: linksPerRow === 1,
    [css.link2Columns]: linksPerRow === 2,
    [css.link3Columns]: linksPerRow === 3,
  });
  const imageWrapperClasses = classNames(imageWrapperClassName || css.imageWrapper);

  const LinkComponentProps = type === 'NamedLink' ? { name, params, to } : { href };
  const LinkComponent = type === 'NamedLink' ? NamedLink : ExternalLink;

  return (
    <div className={classes}>
      <LinkComponent {...LinkComponentProps}>
        <div className={imageWrapperClasses}>
          <div className={css.aspectWrapper}>
            <img src={imageUrl} alt={imageAltText} className={css.image} />
          </div>
        </div>
      </LinkComponent>
      <div className={css.text}>{text}</div>
    </div>
  );
};

如果我像下面这样写 map function 是否正确?

链接:data.slice(3).map(item => getImageLinkObject(item), (key = item))

感谢您的所有帮助。 如果问题不符合要求,我将删除该问题,请通知我。

编辑:为跟踪 map function 的问题添加了 BlogThumbnailLinks 和 ThumbnailLink 组件代码。

如果@about14sheep 没有找到正确的位置,请尝试在getImageLinkObject 的文本元素中添加一个键。

编辑:尝试将密钥移动到片段

尝试这个:

const getImageLinkObject = data => ({
    imageAltText: 'TravelerStories Post Image',
    imageUrl: data.data.image.url,
    linkProps: {
      type: 'NamedLink',
      name: 'TravelerStoriesPage',
      to: {
        pathname: `/stories/${data.uid}`,
      },
    },
    text: (
      <React.Fragment key={item.uid}>
        <HeadLine post={data} type="stories">
          <RichText render={data.data.title} />
          &nbsp;
          <RichText render={data.data.sub_title} />
        </HeadLine>
        <br />
        <TagList post={data} />
      <\ React.Fragment>
    ),
  });

所以这是一个常见的问题,当列出项目或使用.map 时反应 但是,我很抱歉,但我在阅读您的代码时遇到了一些麻烦。 我想我找到了你需要添加密钥的地方,试试这个:

<LayoutWrapperMain>
      <div className={css.blogContainer}>
        {fetchError}
        {noResult}
        {firstPost && <BlogThumbnailLinks key="1" {...firstPost} />}
        {secondRow && <BlogThumbnailLinks key="2" {...secondRow} />}
        {_3x3Grid && <BlogThumbnailLinks key="3" {..._3x3Grid} />}

      </div>
</LayoutWrapperMain>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM