0

I am having trouble with a little React Router exercise

I have a File "ItemDetail.js" in which i define a Componenet "ItemDetail" which is called by a Route from another Component like so: <Route path="/shop/:id" component={ItemDetail}/>. This is my code:

import React, {useState, useEffect} from "react";

function ItemDetail({ match }) {

    const [item, setItem] = useState({});
    console.log(match);

    useEffect(() => {
        fetchItem();
    }, []);

    
    const fetchItem = async () => {
        const data = await fetch(`https://fortnite-api.theapinetwork.com/item/get?id=${match.params.id}`);
        const item = await data.json();
        setItem(item);
        console.log(item);
    }


    return (
      <div>
          <h3>Item: {item.data.item.name}</h3>
      </div>
  );
}

export default ItemDetail;

The id of the path is an id that i need for the API call (over match.params.id).

The fetchItem() method fetches the data I need as a JSON, I then save it in the state item and then render my render stuff. But it throws an error with : "TypeError: Cannot read property 'item' of undefined" in the line where i access item.data.name from JSX.

Weird thing is: when I change <h3>Item: {item.data.item.name} </h3> to <h3>Item: </h3> and then back to <h3>Item: {item.data.item.name} </h3> It works, just this one time, and when i refresh or navigate through my site to this path again it throws this error again

3
  • 1
    On your initial render, item is only set to {}, which means trying to access item.data.item will blow up (you'd be trying to access item on undefined). One common pattern to avoid this is to have something display while it's loading from the fetch call: <h3>Item: {item.data ? item.data.item.name : "Loading..."}</h3> Commented Dec 30, 2020 at 18:33
  • Ohh so it's because the code in return is is done executing before fetchItem()? Thanks that worked for me Commented Dec 30, 2020 at 18:38
  • Yeah, the fetch happens asynchronously, so there's definitely a render that happens before the fetch returns and you set state. Commented Dec 30, 2020 at 18:40

1 Answer 1

1

You need control it because react renders before you get item. You can edit like this : So it will check when you got item it will render the page.

import React, {useState, useEffect} from "react";

function ItemDetail({ match }) {

    const [item, setItem] = useState({});
    console.log(match);

    useEffect(() => {
        fetchItem();
    }, []);

    
    const fetchItem = async () => {
        const data = await fetch(`https://fortnite-api.theapinetwork.com/item/get?id=${match.params.id}`);
        const item = await data.json();
        setItem(item);
        console.log(item);
    }


    return (
      <div>
         <h3>Item: {item && item.data && item.data.item ? item.data.item.name : "Loading..."}</h3>
      </div>
  );
}

export default ItemDetail;

Also you can use optional chaining for the short solution : <h3>Item: {item?.data?.item ? item.data.item.name : "Loading..."}</h3>

Sign up to request clarification or add additional context in comments.

4 Comments

Yes i forgot it, you are right. I'm editing the code.
Also i added item and item.data and item.data.item checks so it will be best check isn't it?
Yeah. Actually another much simpler option, if optional chaining is available, would be to just do something like this: item?.data?.item?.name || "Loading..."
I know that maybe it's not available so i added far way of solution. But it's possible.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.