Search code examples
javascriptreactjsreact-router-dom

Error when react componnent tries to rerender right before the page should switch


I have a react app where I switch between two pages (automatic and manual). Each of these two pages shows different data.

First I get from the server list of automatic or manual batches (depending on the pages) and then I make a table for them and for each batch I call the server again to get data of the batch.

So when I call "/api/manual/list_batches" and I get list of batch names among them for example a batch of name "foo_manual_batch" I can call "/api/manual/get_batch?batch_name=foo_manual_batch"

The problem is that when I switch to the automatic page, I expect it to discard the whole table and call this list_batches again but for automatic: "/api/automatic/list_batches" and then call "api/automatic/get_batch?batch_name=..." for every new batch name.

However what happens is that right before the new list of batches is obtained, The table sees that the "mode" variable obtained from the URL has changed from manual to automatic and tries to rerender the rows by calling "/api/automatic/get_batch?batch_name=foo_manual_batch" which gives an error.

The page works fine, but there are these unnecessary server calls happening on switching between automatic and manual and I struggle few days to find how to fix it.

Here I created a minimal working example where you can see the error logs while switching between automatic and manual indicating the unwanted api calls.

App

import React from "react";
import { Route, Switch } from "react-router-dom";

import BatchesPage from "./BatchesPage";
import Navigation from "./Navigation";
import "./styles.css";
export default function App() {
  return (
    <div>
      <Switch>
        <Route exact path="/">
          <Navigation />
        </Route>
        <Route exact path="/:mode">
          <Navigation />
          <BatchesPage />
        </Route>
      </Switch>
    </div>
  );
}

BatchesPage

import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";

import BatchesTable from "./BatchesTable";
import { fetchJson } from "./model";

export default function BatchesPage() {
  const [batchesNames, setBatchesNames] = useState([]);
  const { mode } = useParams();
  const getBatchesNamesCmd = `/api/${mode}/list_batches`;

  // loads automatic batches names or manual batches names depending on the mode
  useEffect(() => {
    fetchJson(getBatchesNamesCmd)
      .then((jsonData) => {
        setBatchesNames(jsonData.sort().reverse());
      })
      .catch((error) => {
        console.error(`error occure: ${error}`);
      });
  }, [getBatchesNamesCmd]);

  return batchesNames ? (
    <div>
      {mode === "automatic" ? (
        <div className="horizontal-container">
          {["fruit", "vegetable"].map((keyword) => (
            <BatchesTable
              key={keyword}
              batchesNames={batchesNames.filter((name) =>
                name.includes(keyword)
              )}
              viewMode="narrow"
              tableName={keyword}
            />
          ))}
        </div>
      ) : (
        <BatchesTable batchesNames={batchesNames} viewMode="wide" />
      )}
    </div>
  ) : (
    <div></div>
  );
}

https://codesandbox.io/s/beautiful-carson-qiwv7d

Can you please help me to figure out how to write the react components correctly so that this issue is not happening?


Solution

  • It seems there is a race condition between the route path changing, e.g. the mode from "automatic" to "manual" and vice versa, and what the current value of the batchesNames in BatchesPage and the batch in BatchRow states prior to them being updated.

    You could go through all the components and check their lifecycle effects and try to reset these state values back to their initial state values, but then there's an issue between React state updates being processed asynchronously and the JSON data fetch immediately happening.

    A super trivial solution is to completely remount a new instance of the BatchesPage component when the route path changes. Use the mode route path parameter as a React key.

    Example:

    export default function App() {
      return (
        <div>
          <Navigation />
          <Switch>
            <Route
              path="/:mode"
              render={({ match }) => <BatchesPage key={match.params.mode} />}
            />
          </Switch>
        </div>
      );
    }
    

    Edit error-when-react-componnent-tries-to-rerender-right-before-the-page-should-switc