morh
morh

Reputation: 101

Is there a better way to get different data from an API in the same component based on data type?

I have a homepage with multiple sections, and I wanted each section to get different data, one for "Popular Recipes" and one for "New Recipes" for example. So I wrote a switch statement inside a useeffect for this but it feels not "the best way" to me.

Home.js:

import React from "react";
import { Container } from "@chakra-ui/react";
import Section from "./Section";

const sections = [
  {
    name: "Popular Recipes",
  },
  { name: "New Recipes" },
];

function Home() {
  return (
    <Container maxWidth="full">
      {sections.map((section, i) => (
        <Section key={i} name={section.name} />
      ))}
    </Container>
  );
}

export default Home;

Section.js:

import React, { useEffect, useState } from "react";
import { Text, Flex, Box, Spacer, SimpleGrid, Heading } from "@chakra-ui/react";
import { getAllRecipes } from "../api";
import Recipe from "../common/Recipe";

function Section(props) {
  const [recipes, setRecipes] = useState([]);
  const [error, setError] = useState("");
  useEffect(() => {
    switch (props.name) {
      case "Popular Recipes":
        getAllRecipes()
          .then((data) => {
            setRecipes(data);
          })
          .catch((error) => setError(error.response.data.error));
    }
  }, []);
  return (
    <Box marginBottom="10">
      <Heading as="h2" marginBottom="5" fontSize="xl" fontWeight="hairline">
        {props.name}
      </Heading>
      <SimpleGrid minChildWidth="250px" columns={3} spacing={6}>
        {error ? (
          <Text>{error}</Text>
        ) : (
          recipes?.map((recipe) => {
            return <Recipe key={recipe._id} recipe={recipe} />;
          })
        )}
      </SimpleGrid>
    </Box>
  );
}

export default Section;

Upvotes: 0

Views: 117

Answers (1)

superhawk610
superhawk610

Reputation: 2653

Instead of depending on props.name, just have your Section component accept a getRecipes prop and pass it explicitly in the parent component. This decouples name from the actual implementation, so changing from Latest Recipes to Latest Posts (or whatever) won't break your app.

// queries.js -----

export const getPopularRecipes = () => fetch('/recipes/popular').then(res => res.json());

export const getLatestRecipes = () => fetch('/recipes/latest').then(res => res.json());

// Section.js -----

function Section(props) {
  // ...

  useEffect(() => {
    props.getRecipes()
      .then(data => setRecipes(data))
      .catch(error => setError(error.response.data.error));
  }, []);

  // ...
}

// Home.js -----

import { getPopularRecipes, getLatestRecipes } from './queries';

function Home() {
  return (
    <Container maxWidth="full">
      <Section name="Popular" getRecipes={getPopularRecipes} />
      <Section name="Latest" getRecipes={getLatestRecipes} />
    </Container>
  );
}

Alternatively, if the logic for fetching recipes is identical (only the API path is different, for example), you can have the Section component accept a recipeType prop (or similar).

// Section.js -----

function Section(props) {
  // ...

  useEffect(() => {
    fetch(`/recipes/${props.recipeType}`)
      .then(res => res.json())
      .then(data => setRecipes(data))
      .catch(error => setError(error.response.data.error));
  }, []);

  // ...
}

// Home.js -----

import { getPopularRecipes, getLatestRecipes } from './queries';

function Home() {
  return (
    <Container maxWidth="full">
      <Section name="Popular Recipes" recipePath="popular" />
      <Section name="Latest Recipes" recipePath="latest" />
    </Container>
  );
}

Either way, decouple your network implementation from your name and you should be resilient to future refactors.

Upvotes: 1

Related Questions