3

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

mattangel
  • 71
  • 4

1 Answers1

0

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>
    
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Thank you for taking the time to write such an in depth solution, the thing is that there is an element that matches the filter because I see it in the console, its an array with a single object that has the same id as the parameter I am appending to the URL. If I remove

    {singleBeer[0].name}

    from my component, I see the single object from my filter method log to the console, when I dont remove it , the component doesnt render at all.
    – mattangel Aug 12 '22 at 22:57
  • @mattangel Well, your console log is an unintentional side-effect so it might not accurately agree with the React component lifecycle when rendering to the DOM. I just pulled up the JSON data in the browser so I can see that `_id` is in fact a string value. Keep in mind that the `beers` state array will be initially an empty array until the fetch resolves and updates the local state. I'll update my answer. – Drew Reese Aug 12 '22 at 23:10