Best practices to manage state and API fetching in React?

Viewed 39

I am learning React for the first time and just shipped my first app.

The app is mounted on a single page in a node/express dashboard app which uses server-side templates. My plan is to gradually extend the React app to replace additional templates within the dashboard.

Everything works, but I have two issues:

  1. There is already quite a lot of state, and the code is filled with props being passed down to components. I feel like this would make the code unmaintainable as the app grows.
  2. The code for communicating with the back-end API is not very declarative. I am manually indicating when to refetch from the API, to prevent double fetching on initial load.

I am looking for advice on best practices in React to handle these two issues. The code showing both issues is provided below.


Questions

  1. What would be the best way for me to manage state in this app as the complexity grows? Would it for example be:
  • Create objects for the useState hook instead of primitives and pass those down?
  • Use component composition to reduce the prop drilling?
  • Use React Context?
  • Use Redux?
  1. Is there a more declarative way of achieving what I am currently doing sync the state with the API?

I am manually disabling fetching initially because the initial query parameters are included in the first object returned from the API but they are not included in the url. So I want to build them when initialising the app without refetching.


Code and stack

I included the main part of the code for the app below, as well as the file tree for the project to give an idea of the structure.

Stack details:

  • TypeScript
  • Vite.js for dev-server and builds
  • SWR for data-fetching
  • Express.js backend with cookie-session for sessions and persistent query parameters

/src/Feedback.txs - holds the state and fetches data accordingly

import { useEffect, useState } from "react";
import useSWR from "swr";

import { getActiveTeamID, getTimescaleValue } from "./lib/util";

import Header from "./components/header/Header";
import Body from "./components/feedback/Body";

export default function Feedback() {
  // API
  const [queryParams, setQueryParams] = useState("");
  const { data, error } = useSWR(`/client/feedback${queryParams}`);

  // Local state
  const [title, setTitle] = useState("Feedback");
  const [translate, setTranslate] = useState(true);
  const [page, setPage] = useState(1);
  const [lng, setLng] = useState("is");
  const [teams, setTeams] = useState([]);
  const [activeTeamID, setActiveTeamID] = useState(0);
  const [showChildren, setShowChildren] = useState(true);
  const [timescale, setTimescale] = useState("last-3");
  const [searchTerm, setSearchTerm] = useState("");
  const [isDummy, setIsDummy] = useState(false);

  // Trigger for refetching
  const [refetch, setRefetch] = useState(false);
  const [pageChange, setPageChange] = useState(false);

  const isLoading = !error && !data;

  // Determine initial state
  useEffect(() => {
    if (data) {
      setLng(data.client.lng);
      setTitle(data.client.title);
      setTeams(data.filters.teams);
      setIsDummy(data.filters.dummy);
      setActiveTeamID(getActiveTeamID(data.filters.teams));
      setShowChildren(data.filters.children);
      setTimescale(getTimescaleValue(data.filters.timescale.selected));
      document.title = `${data.client.title}`;
    }
  }, [data]);

  // Refetch from API
  useEffect(() => {
    if (refetch) {
      const pageNoToUse = pageChange ? page : 1;
      setQueryParams(
        `?team=${activeTeamID}&children=${showChildren}&view=${timescale}&search=${searchTerm}&page=${pageNoToUse}`
      );
    }
    setPageChange(false);
    setRefetch(false);
  }, [refetch]);

  if (error) {
    return <div>{error}</div>;
  }

  return (
    <>
      <Header
        title={title}
        lng={lng}
        filters={data?.filters}
        translate={translate}
        setTranslate={setTranslate}
        setQueryParams={setQueryParams}
        teams={teams}
        setTeams={setTeams}
        activeTeamID={activeTeamID}
        setActiveTeamID={setActiveTeamID}
        showChildren={showChildren}
        setShowChildren={setShowChildren}
        timescale={timescale}
        setTimescale={setTimescale}
        searchTerm={searchTerm}
        setSearchTerm={setSearchTerm}
        setRefetch={setRefetch}
      />
      <Body
        feedback={data?.feedback}
        lng={lng}
        translate={translate}
        page={page}
        setPage={setPage}
        pagination={data?.pagination}
        isLoading={isLoading}
        setRefetch={setRefetch}
        setPageChange={setPageChange}
        searchTerm={searchTerm}
        setSearchTerm={setSearchTerm}
        isDummy={isDummy}
      />
    </>
  );
}

/src/main.txs - this is the entry point to the app

import "vite/modulepreload-polyfill";

import React from "react";
import ReactDOM from "react-dom/client";
import Feedback from "./Feedback";
import { SWRConfig } from "swr";
import fetcher from "./lib/util/fetcher";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <SWRConfig
      value={{
        fetcher: fetcher,
      }}
    >
      <Feedback />
    </SWRConfig>
  </React.StrictMode>
);

File tree

client
┣ dist
┃ ┣ assets
┃ ┃ ┗ main.b889a7af.js
┃ ┗ manifest.json
┣ node_modules
┃ ┣ ...
┣ src
┃ ┣ components
┃ ┃ ┣ feedback
┃ ┃ ┃ ┣ chat
┃ ┃ ┃ ┃ ┣ CommentEmployee.tsx
┃ ┃ ┃ ┃ ┣ CommentInitial.tsx
┃ ┃ ┃ ┃ ┣ CommentManager.tsx
┃ ┃ ┃ ┃ ┣ Footer.tsx
┃ ┃ ┃ ┃ ┣ Panel.tsx
┃ ┃ ┃ ┃ ┗ PanelContent.tsx
┃ ┃ ┃ ┣ Body.tsx
┃ ┃ ┃ ┣ Card.tsx
┃ ┃ ┃ ┗ Pagination.tsx
┃ ┃ ┣ header
┃ ┃ ┃ ┣ filters
┃ ┃ ┃ ┃ ┣ ActiveButtons.tsx
┃ ┃ ┃ ┃ ┣ ChildrenFilter.tsx
┃ ┃ ┃ ┃ ┣ Filters.tsx
┃ ┃ ┃ ┃ ┗ TeamFilter.tsx
┃ ┃ ┃ ┣ other
┃ ┃ ┃ ┃ ┣ DownloadExcel.tsx
┃ ┃ ┃ ┃ ┣ Search.tsx
┃ ┃ ┃ ┃ ┣ Title.tsx
┃ ┃ ┃ ┃ ┗ Translations.tsx
┃ ┃ ┃ ┣ timescale
┃ ┃ ┃ ┃ ┣ SubmenuRange.tsx
┃ ┃ ┃ ┃ ┣ SubmenuSurvey.tsx
┃ ┃ ┃ ┃ ┗ Timescale.tsx
┃ ┃ ┃ ┗ Header.tsx
┃ ┣ lib
┃ ┃ ┣ util
┃ ┃ ┃ ┣ classnames.ts
┃ ┃ ┃ ┣ fetcher.ts
┃ ┃ ┃ ┗ index.ts
┃ ┃ ┗ translate.ts
┃ ┣ Feedback.tsx
┃ ┣ main.tsx
┃ ┗ vite-env.d.ts
┣ .eslintrc.json
┣ package-lock.json
┣ package.json
┣ tsconfig.json
┣ tsconfig.node.json
┗ vite.config.ts
0 Answers
Related