Search code examples
javascriptreactjsreact-hooksreact-reduxreact-router-dom

Throws Uncaught TypeError: Cannot read properties of undefined (reading 'includes') in the first render


I want to map through an array that is inside an object and see if the array contains the teacherId and store it in a state, then map it so the <Section /> component can render it.

const teacherId = details.id;

const [availableSection, setAvailableSection] = useState([])

useEffect(() => {
  const availSection = sections.map((section) => (
    section.teachers.includes(teacherId) ? section : null
  ))
        
  setAvailableSection(availSection);

}, [sections])

In the first transition to the "/dashboard" page it returns an error, Uncaught TypeError: Cannot read properties of undefined (reading 'includes') but if I refresh the page again it will render just fine. How can I fix this problem?

After going to the "/dashboard" page, I want to instantly show the <Section /> component.

here's my code:

parent:

function Dashboard() {
  const dispatch = useDispatch(getSections())

  useEffect(() => {
    dispatch(getSections());
  }, [dispatch])

  const location = useLocation()

  return (
    <>
      <h1>Hello {location.state.teachersDetail.userName}</h1>
      <h1>This is where the chart and cards are located</h1>
      <Sections details={location.state.teachersDetail} />
    </>
  )
}

export default Dashboard;

Sections component:

function Sections({ details }) {
  const sections = useSelector((state) => state.sections)

  console.log(sections);

  const teacherId = details.id;

  const [availableSection, setAvailableSection] = useState([])

  useEffect(() => {
    const availSection = sections.map((section) => (
      section.teachers.includes(teacherId) ? section : null
    ))
        
    setAvailableSection(availSection);
  }, [sections])

  return (
    !sections.length
      ? <CircularProgress />
      : (
        <Grid
          container
          direction="column"
          justifyContent="flex-start"
          alignItems="stretch"
          style={{margin:'0 auto'}}
        >
          {availableSection.map((section) => (
            section !== null
              ? (
                <Grid key={section._id} item xs={12} sm={6}>
                  <Section section={section}/>
                </Grid>
              )
              : null
          ))}
        </Grid>
      ) 
  )
}

export default Sections

here's the section object:

sectionName: "ICT-5B", teachers: (3) ['1', '2', '3'], _id: "63f493ed77a87fdd1723d553"


Solution

  • section.teachers is undefined. If you are really just wanting to filter the sections array if there is a teachers array that includes teacherId then I suggest the following that uses a null-check on the potentially undefined section.teachers property.

    const sections = useSelector((state) => state.sections);
    
    const teacherId = details.id;
    
    const [availableSection, setAvailableSection] = useState([]);
    
    useEffect(() => {
      const availSection = sections.filter(
        (section) => section.teachers?.includes(teacherId)
      );
        
      setAvailableSection(availSection);
    }, [sections, teacherId]);
    
    ...
    

    Also note that availableSection is what we consider to be derived state and it's a general React anti-pattern to store derived state in local React state. Just about any time you have a useState/useEffect pair what you really want to use is the useMemo hook.

    Example:

    const sections = useSelector((state) => state.sections);
    
    const teacherId = details.id;
    
    const availableSection = useMemo(() => {
      return sections.filter(
        (section) => section.teachers?.includes(teacherId)
      );
    }, [sections, teacherId]);
    
    ...
    

    If you don't need the selected sections state other than to compute the availableSection value then you can move the logic into the useSelector hook.

    Example:

    const teacherId = details.id;
    
    const availableSection = useSelector((state) => state.sections.filter(
      (section) => section.teachers?.includes(teacherId)
    ));
    
    ...