Why am I getting Uncaught TypeError: Cannot read properties of undefined (reading 'name')

Viewed 183

I am trying to display a specific beer based on the beer id I am appending to the url

import React from "react";
import { useState, useEffect } from "react";
import axios from "axios";
import {
  Routes,
  Route,
  Link,
  useNavigate,
  useLocation,
} from "react-router-dom";

export default function SingleBeer() {
  const [beers, setBeers] = useState([]);
  useEffect(() => {
    axios
      .get(`https://ih-beers-api2.herokuapp.com/beers`)
      .then((response) => setBeers(response.data));
  }, []);
  const location = useLocation();
  let beerId = location.pathname.split("/").pop();
  const navigate = useNavigate();

  let singleBeer = beers.filter((beer) => beer._id === beerId);
console.log(singleBeer)

  return (
    <div>
      <h1>Single Beer</h1>
      <h5 onClick={() => navigate("/beers")}> Go Back</h5>
      <h1>{singleBeer[0].name}</h1>
    </div>
  );
}

When I click show Details button for the corresponding beer on my Beers.js component the SingleBeer component doesn't render, instead I get the error "Why am I getting Uncaught TypeError: Cannot read properties of undefined (reading 'name')"

If I remove the 'h1' with the singleBeer[0].name, I can see the console.log for the single beer im not sure why the 'name' property of the single beer object is coming back as undefined if the object shows up in the console, all I am trying to do is access the specific beer route when I click show details in the previous component

1 Answers

Issues

I think there are a few possible issues happening here.

  1. The beers state array is initially empty from the time the component mounts and through any subsequent render cycles until the axios GET request resolves and enqueues a beers state update that can be filtered.

  2. Array.protptype.filter can possibly return an empty array if no elements match the predicate function. In this case singleBeer[0] is undefined and an error will be thrown if attempting to access singleBeer[0].name.

  3. There might be a type comparison mismatch between beerId which will be a string type, and beer._id which might not be a string.

    let beerId = location.pathname.split("/").pop(); // <-- string type
    

    You want to type-safe comparisons when using strict equality (===).

Solution

  • If necessary, use a type-safe comparison by converting/ensuring both sides are strings.

     const singleBeer = beers.filter((beer) => String(beer._id) === beerId);
    
  • Gracefully handle if no match is found.

     if (!singleBeer) return "Sorry, tapped out. No beer here.";
    
     return (
       <div>
         <h1>Single Beer</h1>
         <h5 onClick={() => navigate("/beers")}> Go Back</h5>
         <h1>{singleBeer[0].name}</h1>
       </div>
     );
    

    If you don't want to render the alternative view then at a minimum use a null-check/guard-clause on the potentially undefined object.

     <h1>{singleBeer[0]?.name}</h1>
    
Related