Search code examples
reactjsfetchuse-effect

useEffect not running on refresh


I'm having an issue with the useEffect hook on this blog site I'm building. Im trying to fetch all the blogs from the backend so I can use them to populate this section with the latest five blogs. When I use the code below, the empty array in the useEffect prevents the infinite amount of fetch calls, which is great.

But then I run into a problem where if I refresh the page or navigate back to it I get an error on line 35 saying "cannot find mainImage of undefined".

My question is how do I have the fetchCall populate the state and do so even on a refresh so that I can still access the info I need. Thanks!

import React, {useState, useEffect} from 'react';
import CardItem from './CardItem';
import './Cards.css';

function Cards() {
  const [blogs, setBlogs] = useState();

  useEffect(() => {
      fetchBlogs();
  // eslint-disable-next-line react-hooks/exhaustive-deps
  },[]);

  const fetchBlogs = async () => {
    console.log('ok');
    const response = await fetch('http://localhost:3000/blogs');
    const data = await response.json();
    setBlogs(data);
  };


  return (
    <div className='cards'>
      <div className='header-container'>
        <img
          className='logo'
          alt='logo'
          src='https://Zi.imgur.com/MaLqLee.png'
        />
        <h1 className='cards-title'>Hot takes and hometown bias</h1>
      </div>
      <div className='cards-container'>
        <div className='cards-wrapper'>
          <ul className='cards-items'>
            <CardItem
              src={blogs.length > 0 ? blogs[0].mainImage : ''}
              text="Don't look now, but Zach Lavine officially kicks ass."
              label='Bulls'
              path='/bulls'
            />
            <CardItem
              src='https://i.imgur.com/EmVgHk2.jpg'
              text='The curious case of Mitch Trubisky apologists.'
              label='Bears'
              path='/bears'
            />
          </ul>
          <ul className='cards-items'>
            <CardItem
              src='https://i.imgur.com/ZZ5dLJU.jpg'
              text='How Did We Get Here? The Suddenly Bleak State of the Cubs.'
              label='Cubs'
              path='/cubs'
            />
            <CardItem
              src='https://i.imgur.com/MwgKmUM.jpg'
              text='Pace and Nagy: So, how much can we blame these guys?'
              label='Bears'
              path='/bears'
            />
            <CardItem
              src='https://i.imgur.com/Y2Eorvu.jpg'
              text='Thad Young: An Ode to the NBA Journeyman.'
              label='Bulls'
              path='/bulls'
            />
          </ul>
        </div>
      </div>
    </div>
  );
}

export default Cards;

Solution

  • The fetch call is asynchronous. This means it is not guaranteed to be complete before the program enters the next line.

    Because of this the blogs array will be empty at the first render. You can add an check in the src CardItem component to only use the value returned from the fetch call when it is available:

    <CardItem
      src={blogs.length > 0 ? blogs[0].mainImage : ''}
      ...
    />
    

    An alternative would be to use the fact that blogs is an array and use the map operator to build one or more CardItems.

    <ul className='cards-items'>
      {blogs.map(blog => <CardItem
        src={blog.mainImage}
        ...
      />)}
    </ul>