简体   繁体   中英

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". It's because courses is empty just after render.

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?

Issue

The issue here is that the CourseInfo component isn't being very defensive about protecting itself against potentially undefined values. 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.

Return value

The first element in the array that satisfies the provided testing function. Otherwise, undefined is returned.

The useEffect hook runs after the component is rendered to the DOM during the "commit phase".

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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