1
\$\begingroup\$
import axios from "axios";
import { useEffect, useState } from "react";
import "./styles.css";

export default function App() {
  const [count, setCount] = useState(1);
  const [titles, setTitles] = useState([]);
  useEffect(() => {
    async function fetchData() {
      const URL1 = `https://jsonplaceholder.typicode.com/todos/${count}`;
      const URL2 = `https://jsonplaceholder.typicode.com/todos/${count + 1}`;

      const apiData = await Promise.all([
        axios.get(URL1),
        axios.get(URL2)
      ]).then((res) => res.map((x) => x.data));
      setTitles((prevTitles) => {
        return [...prevTitles, ...apiData];
      });
    }

    fetchData();
  }, [count]);

  const fetchMore = () => {
    setCount(count + 2);
  };
  return (
    <div className="App">
      <div className="title">Fetch Todos</div>
      <ul>
        {titles &&
          titles.map((x) => (
            <li key={x.id}>
              {x.id} - {x.title}
            </li>
          ))}
      </ul>
      <button onClick={fetchMore}>Fetch More</button>
    </div>
  );
}

I am pretty sure that json calls are usually put in their own files, but I kinda forgot how to do it and what are the best practices concerning API calls.

https://codesandbox.io/s/g8c7b?file=/src/App.js:0-1065

I think where you put the json calls also depend on whether you use Redux or any other state management library.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

I will first suggest some miscellaneous changes to the code and then answer your questions.

A better count handling

I think that naming the state count is a bit misleading because it is used as an offset to fetch the todos. This may count as a nit, but when you look at the code as a whole I think it is more accurate to use offset instead of count.

Usually, when I have a bit of state that always mutates in the same way, I use useReducer. This way, the code looks a bit cleaner:

const [offset, doubleOffset] = useReducer((c) => c + 2, 1);
...
<button onClick={doubleOffset}>Fetch More</button>

In this particular case, we remove the need for a click handler. The updater function of useReducer is enough, and the code is more concise.


Space and unnecessary braces

This can also be a bit subjective, but I find that adding spaces and blank lines properly improves readability a lot. I would update your code to be like this, only adding blank lines and removing extra braces:

  const [count, setCount] = useState(1);
  const [titles, setTitles] = useState([]);

  useEffect(() => {
    async function fetchData() {
      const URL1 = `https://jsonplaceholder.typicode.com/todos/${count}`;
      const URL2 = `https://jsonplaceholder.typicode.com/todos/${count + 1}`;

      const apiData = await Promise.all([
        axios.get(URL1),
        axios.get(URL2)
      ]).then((res) => res.map((x) => x.data));

      setTitles((prevTitles) => [...prevTitles, ...apiData]);
    }

    fetchData();
  }, [count]);

  const fetchMore = () => setCount(count + 2);

Services

Usually, an easier way to think about interactions of your app with another app is to treat them as black boxes, which means trying to call functions so your component's logic is decoupled from other modules in your app, like handling API calls, or generating a pdf, or opening a WebSocket. This is called service because those functions provide services for your UI logic.

This also means that it is easier to mock and test your code, so that is a plus. This allows for better code colocation, because usually, services group code that is related, so it is easier to have a wider view of the abstraction.

I know that I am using somewhat subjective terms here, but I hope you'll understand what I mean with the code examples.

Todos API

In this particular example, the service would be the interface with the Todos API. So I think that it's fair to extract that logic to a separate file.

Let's call the service todosApi and the containing file todosApi.js. Inside this file, we will define the functions that our service exposes and export an object for components to use.

Usually, different services that are related are placed in the same folder, for example, say we have a likes API that we want to query besides our todos API. Then we would have an index.js re-exporting those services so that we have auto-import and autocomplete in our IDE.

// todosApi.js
import axios from 'axios';

const getTodoById = id => { ... };

export const todosApi = {
  getTodoById
};

Using the service

Now that we have our service setup, we use it inside our component:

  // App.js
  useEffect(() => {
    async function fetchData() {
      const apiData = await todosApi.getTodoById(offset);
      ...
    }

    fetchData();
  }, [ ... ]);

Notice that this removes the need to import axios in our component.


A better structure

Personally, I think that code split into simple, well-defined functions is a bit better than having logic pervading the codebase, so I polished the code until I was comfortable with it. My fork is here.

// todosApi.js
import axios from "axios";

const getTodoById = (id) =>
  axios
    .get(`https://jsonplaceholder.typicode.com/todos/${id}`)
    .then((res) => res.data);

const fetchTodoPair = (offset) =>
  Promise.all([getTodoById(offset), getTodoById(offset + 1)]);

export const todosApi = {
  getTodoById,
  fetchTodoPair
};
// App.js
import { useEffect, useReducer, useState } from "react";
import { todosApi } from "./todosApi";
import "./styles.css";

export default function App() {
  const [offset, doubleOffset] = useReducer((c) => c + 2, 1);
  const [titles, setTitles] = useState([]);

  useEffect(() => {
    const fetchTodoPair = async () => {
      const newTitles = await todosApi.fetchTodoPair(offset);

      setTitles((prevTitles) => [...prevTitles, ...newTitles]);
    };

    fetchTodoPair();
  }, [offset]);

  return (
    <div className="App">
      <div className="title">Fetch Todos</div>
      <ul>
        {titles &&
          titles.map((x) => (
            <li key={x.id}>
              {x.id} - {x.title}
            </li>
          ))}
      </ul>
      <button onClick={doubleOffset}>Fetch More</button>
    </div>
  );
}
\$\endgroup\$

Your Answer

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