简体   繁体   English

如何在 React-redux 渲染之前从商店获取数据?

[英]How to get data from store before render in React-redux?

I need to render component by route "courses/:courseId" , but I got an error "Uncaught TypeError: Cannot read properties of undefined".我需要通过路由"courses/:courseId"渲染组件,但出现错误“Uncaught TypeError: Cannot read properties of undefined”。 It's because courses is empty just after render.这是因为courses在渲染之后是空的。

import React, { useEffect, useState } from 'react';
import { useParams, Link, useNavigate } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';

import { pipeDuration } from '../../helpers/pipeDuration';
import { transformDate } from '../../helpers/dateGenerator';
import { selectCourses, selectAuthors } from './selectors';
import { BACK_TO_COURSES } from '../../constants';

import classes from './CourseInfo.module.css';

import { getCourses } from '../../store/courses/thunk';
import { getAuthors } from '../../store/authors/thunk';

const CourseInfo = () => {
  useEffect(() => {
    dispatch(getAuthors());
    dispatch(getCourses());
  }, []);

  const dispatch = useDispatch();
  const { courseId } = useParams();
  const courses = useSelector(selectCourses);
  const authors = useSelector(selectAuthors);
  const course = courses.find((course) => course.id === courseId);

  const createdDate = transformDate(course.creationDate);
  const duration = pipeDuration(course.duration);

  const courseAuthors = course.authors.map((authorId) => {
    return authors.find(({ id }) => id === authorId);
  });

  const courseAuthorsList = courseAuthors.map((author, index, array) => {
    return index + 1 === array.length ? author.name : author.name + ', ';
  });

  return (
    <div className='container'>
      <div className={classes.wrapper}>
        <Link to='/courses'>{BACK_TO_COURSES}</Link>
        <h2 className={classes.title}>{course.title}</h2>
        <div className={classes.info}>
          <p className={classes.description}>{course.description}</p>
          <div className={classes.details}>
            <div><strong>ID: </strong>{courseId}</div>
            <div><strong>Duration: </strong>{duration} hours</div>
            <div><strong>Created: </strong>{createdDate}</div>
            <div><strong>Authors: </strong>
              <div className={classes.authorsWrapper}>
                {courseAuthorsList}
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default CourseInfo;

How can I initialize courses const before render?如何在渲染之前初始化courses常量?

Issue问题

The issue here is that the CourseInfo component isn't being very defensive about protecting itself against potentially undefined values.这里的问题是CourseInfo组件在保护自己免受潜在未定义值的影响方面并不是很有防御性。 Assuming the selected courses state is an array you should keep in mind that Array.prototype.find returns undefined when no array element is matched back the predicate callback function.假设所选courses状态是一个数组,您应该记住,当没有数组元素与谓词回调函数匹配时, Array.prototype.find返回undefined

Return value返回值

The first element in the array that satisfies the provided testing function.满足提供的测试函数的数组中的第一个元素。 Otherwise, undefined is returned.否则,返回undefined

The useEffect hook runs after the component is rendered to the DOM during the "commit phase". useEffect挂钩在组件在“提交阶段”呈现给 DOM 后运行。

Your current code:您当前的代码:

const CourseInfo = () => {
  useEffect(() => {
    dispatch(getAuthors());
    dispatch(getCourses());
  }, []);

  const dispatch = useDispatch();
  const { courseId } = useParams();
  const courses = useSelector(selectCourses);
  const authors = useSelector(selectAuthors);

  const course = courses.find((course) => course.id === courseId); // <-- potentially undefined

  const createdDate = transformDate(course.creationDate); // <-- potential undefined access
  const duration = pipeDuration(course.duration); // <-- potential undefined access

  const courseAuthors = course.authors.map((authorId) => { // <-- potential undefined access
    return authors.find(({ id }) => id === authorId);
  });

  const courseAuthorsList = courseAuthors.map((author, index, array) => {
    return index + 1 === array.length ? author.name : author.name + ', ';
  });

  return (
    <div className='container'>
      <div className={classes.wrapper}>
        <Link to='/courses'>{BACK_TO_COURSES}</Link>
        <h2 className={classes.title}>
          {course.title} // <-- potential undefined access
        </h2>
        <div className={classes.info}>
          <p className={classes.description}>
            {course.description} // <-- potential undefined access
          </p>
          <div className={classes.details}>
            <div><strong>ID: </strong>{courseId}</div>
            <div><strong>Duration: </strong>{duration} hours</div>
            <div><strong>Created: </strong>{createdDate}</div>
            <div><strong>Authors: </strong>
              <div className={classes.authorsWrapper}>
                {courseAuthorsList}
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

Solution解决方案

A simple solution would be to check for a defined courses state value prior to attempting to access into the object.一个简单的解决方案是在尝试访问对象之前检查定义的courses状态值。

const CourseInfo = () => {
  useEffect(() => {
    dispatch(getAuthors());
    dispatch(getCourses());
  }, []);

  const dispatch = useDispatch();
  const { courseId } = useParams();
  const courses = useSelector(selectCourses);
  const authors = useSelector(selectAuthors);

  const course = courses.find((course) => course.id === courseId);

   // Check for falsey course value
  if (!course) {
    return <div>No courses found.</div>;
  }

  const createdDate = transformDate(course.creationDate);
  const duration = pipeDuration(course.duration);

  // Guard against undefined course.authors and author arrays
  const courseAuthors = (course.authors || [])
    // Map found author objects
    .map((authorId) => (authors || []).find(({ id }) => id === authorId))
    // Filter any undefined "holes" for unknown authors
    .filter(Boolean)
    // Map to author's name property
    .map((author) => author.name)
    // Join into comma-separated list string
    .join(", ");

  return (
    <div className='container'>
      <div className={classes.wrapper}>
        <Link to='/courses'>{BACK_TO_COURSES}</Link>
        <h2 className={classes.title}>
          {course.title}
        </h2>
        <div className={classes.info}>
          <p className={classes.description}>
            {course.description}
          </p>
          <div className={classes.details}>
            <div><strong>ID: </strong>{courseId}</div>
            <div><strong>Duration: </strong>{duration} hours</div>
            <div><strong>Created: </strong>{createdDate}</div>
            <div><strong>Authors: </strong>
              <div className={classes.authorsWrapper}>
                {courseAuthors}
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

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

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