SkyeBoniwell
SkyeBoniwell

Reputation: 7092

Append data from a fetch call to an API to my state

I have this function below, that runs a function inside a forEach loop, like this:

async populateSystemData(id) {
    const response = await fetch('https://localhost:44389/api/systemlist/GetSystems/' + id);
    const data = await response.json();
    const result = Object.values(data);
    result.forEach((s) => {
        this.populatePlanetData(s.systemId);
    });
    this.setState({ systems: result, loading: false });
}

Here is the function that is executed inside the loop:

async populatePlanetData(id) {
    const response = await fetch('https://localhost:44389/api/planetlist/GetPlanets/' + id);
    const data = await response.json();
    const result = Object.values(data);
    this.setState({ planets: result, loading: false });
}

Here is how I'm writing this data out so the user can see it:

{systems.map(s =>
    <tr>
        <td>{s.SystemName}</td>
        <td>
            <table>
                <tr>
                    <td>Planets</td>
                </tr>
                {planets.map(p =>
                    <tr>
                        <td>{p.PlanetName}</td>
                    </tr>
                )}                                      
            </table>
       </td>
    </tr>
 )}

What's the best way to update the state inside the populatePlanetData so that it appends rather than overwrites?

thanks!

Upvotes: 1

Views: 1499

Answers (3)

Julian Kleine
Julian Kleine

Reputation: 1547

Another approach to better leverage React components

based on the authors comment

there could be 200+ systems, with each system having 1 and up to 50 planets

Instead of cycling through functions and having one global state for loading, consider splitting into components.

Conceptually we need three components

  • Systems - Load all systems and display all systems
  • System - Load all planets from system and display planets
  • Planet - Display content of planet

Let's go ahead and implement them

  1. Systems
function Systems({ id }) {
  const [systems, setSystems] = useState([]);

  useEffect(() => {
    const getSystems = async () => {
      const response = await fetch(
        `https://localhost:44389/api/systemlist/GetSystems/${id}`,
      );
      const data = await response.json();
      setSystems(Object.values(data));
    };
    getSystems();
  }, [id]);

  return (
    <table>
      {systems.map((system) => (
        <System system={system} />
      ))}
    </table>
  );
}
  1. System
function System({ system: { id, SystemName } }) {
  const [planets, setPlanets] = useState([]);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    const getPlanets = async () => {
      const response = await fetch(
        `https://localhost:44389/api/systemlist/GetSystems/${id}`,
      );
      const data = await response.json();
      setPlanets(Object.values(data));
      setIsLoading(false);
    };
    getPlanets();
  }, [id]);

  return (
    <tr>
      {isLoading && <td>Loading...</td>}
      {!isLoading && (
        <>
          <td>{SystemName}</td>
          <td>
            <table>
              <tr>
                <td>Planets</td>
              </tr>
              {planets.map((planet) => (
                <Planet planet={planet} />
              ))}
            </table>
          </td>
        </>
      )}
    </tr>
  );
}
  1. Planet
const Planet = ({ planet: { PlanetName } }) => (
  <tr>
    <td>{PlanetName}</td>
  </tr>
);

What do you think about this approach?

Upvotes: 2

Julian Kleine
Julian Kleine

Reputation: 1547

You can do that by spreading the oldState values

this.setState(({ planets }) => ({ planets: [...planets, ...result], loading: false }));

As a little note - You should not set the loading in populatePlanetData. Rather use a map instead of a forEach and put a await Promise.all(result.map(...)) around it, after you waited for all async operations you can go and set the loading state false.

UPDATE with changes from discussion in answer from @zemil

async populateSystemData(id) {
  const api = `https://localhost:44389/api`;
  const response = await fetch(`${api}/systemlist/GetSystems/${id}`);
  const data = await response.json();
  const systems = Object.values(data);

  this.setState({ systems, loading: false });

  // good way to get async list data
  const planetsData = await Promise.all(
    systems.reduce(async (allSystemsPromise, system) => {
      const allSystems = await allSystemsPromise;
      const res = await fetch(
        `${api}/planetlist/GetPlanets/${system.systemId}`,
      );
      const planetData = await res.json();

      return [...allSystems, ...system];
    }, Promise.resolve([])),
  );
  const planets = planetsData.map((planet) => Object.values(planet));

  this.setState({ planets, loading: false });
}

Upvotes: 1

zemil
zemil

Reputation: 5066

async populateSystemData(id) {
    const response = await fetch(`https://localhost:44389/api/systemlist/GetSystems/${id}`);
    const data = await response.json();
    const systems = Object.values(data);

    this.setState({ systems, loading: false });

    // good way to get async list data
    const planetsData = await Promise.all(systems.map(async (system) => {
      const res = await fetch(`https://localhost:44389/api/planetlist/GetPlanets/${system.systemId}`);
      const planetData = await res.json();

      return planetData;
    }));
    const planets = planetsData.map(planet => Object.values(planet));

    this.setState({ planets, loading: false });
}

Upvotes: 2

Related Questions