Shayne
Shayne

Reputation: 155

React Asynchronous Fetching

Using React and React-Dom CDN 16

I am new to React and trying to build a dashboard component that takes the value of one of three buttons in a Buttons component and sends the value to a List component. The List component fetches data from an API and renders the results.

The feature works fine up until the data fetching, which it only does once the app is rendered the first time. I've logged that the state that's set by the Buttons component is making its way to the List component and the fetch action is updating dynamically correctly, but the fetching functionality isn't getting triggered when that state updates.

Here's the code.

const { useState, useEffect } = React

const App = props => {
  return (
    <div className="app-content">
      <Dashboard />
    </div>
  );
};

const Dashboard = props => {

  const [timespan, setTimespan] = useState('week');
  const changeTime = time => setTimespan(time);

  return(
    <div>
      <p>{timespan}</p> // this state updates when the buttons are clicked
      <Buttons onUpdate={changeTime} />
      <List timespan={timespan}/>
    </div>
  );

};

const Buttons = props => {
  return (
    <div>
      <button onClick={props.onUpdate.bind( this, 'week' )}>
        week
      </button>
      <button onClick={props.onUpdate.bind( this, 'month' )}>
        month
      </button>
      <button onClick={props.onUpdate.bind( this, 'all' )}>
        all
      </button>
    </div>
  );
};

const List = props => {

  const timespan = props.timespan;
  const homepage = `${location.protocol}//${window.location.hostname}`;
  const action = `${homepage}?fetchDataset=1&timespan=${timespan}`; 
  // if I console.log(action) here the URL is updated correctly 

  const [error, setError] = useState(null);
  const [isLoaded, setIsLoaded] = useState(false);
  const [obj, setObj] = useState([]);

  useEffect(() => {
    fetch(action)
    .then(res => res.json())
    .then(
      (result) => { // if I console.log(result) here I only get a response at initialization 
        setIsLoaded(true);
        setObj(result);
      },
      (error) => {
        setIsLoaded(true);
        setError(error);
      }
    )
  }, []);

  if (error) {
    return <div>Error: {error.message}</div>;
  } else if (!isLoaded) {
    return <div>Loading...</div>;
  } else {
    return (
      <div> 
        // my API returns "timespan is: $timespan", but this is only ever "week" because that's the initial state of the timespan 
        {obj}
      </div>
    );
  };
};

ReactDOM.render(
  <App />,
  document.getElementById('app')
);

I think I must be overlooking something very obvious because this seems like one of the core purposes of React, but it's hard to find documentation that is relevant with version 16 updates like function classes and hooks.

I really appreciate any help. Thanks!

Upvotes: 2

Views: 66

Answers (1)

itaydafna
itaydafna

Reputation: 2086

you need to add timeSpan (or action) to your useEffect dependency array:

useEffect(() => {
    fetch(action)
      .then(res => res.json())
      .then(
        result => {
          setIsLoaded(true);
          setObj(result);
        },
        error => {
          setIsLoaded(true);
          setError(error);
        }
      );
  }, [timeSpan]); // [action] will also solve this

This way the effect will know it needs to run every time the timeSpan prop changes.

By passing an empty dependency array you are telling the effect to only run once - when the component it mounted.

Upvotes: 1

Related Questions