Main Pages — Walkthrough: Init, State & Data Fetching

Step 1: Route & Browser Title

const MyEntities = () => {
  const dispatch = useAppDispatch();
  const { browserTitle } = useUpdateRoute({ pathname: "my-entities" });

  React.useEffect(() => {
    document.title = browserTitle;
  }, [browserTitle]);

The pathname must be registered in AppRoutes.tsx and NavRoutes.ts.

Step 2: API Version & URL Parameters

  const apiVersion = useAppSelector(
    (state) => state.global.environment.api_version
  ) as string;

  const { page, setPage, perPage, setPerPage, searchValue, setSearchValue } =
    useListPageSearchParams();

Step 3: Data Fetching Query

  const firstIdx = (page - 1) * perPage;
  const lastIdx = page * perPage;

  const dataResponse = useGettingMyEntitiesQuery({
    searchValue: "",
    sizeLimit: 0,
    apiVersion: apiVersion || API_VERSION_BACKUP,
    startIdx: firstIdx,
    stopIdx: lastIdx,
  } as GenericPayload);

  const { data: batchResponse, isLoading, isFetching, error } = dataResponse;

Step 5: Error Handling

  const globalErrors = useApiError([]);

  React.useEffect(() => {
    if (isFetching) {
      globalErrors.clear();
    }
  }, [isFetching]);

  React.useEffect(() => {
    if (!isLoading && !isFetching && dataResponse.isError) {
      window.location.reload();
    }
  }, [dataResponse.isError, isLoading, isFetching]);

Step 6: Refresh Handler

  const refreshData = () => {
    setIsSearchActive(false);
    setSearchData(null);
    clearSelectedEntities();
    dataResponse.refetch();
  };

  React.useEffect(() => {
    if (!isSearchActive) {
      dataResponse.refetch();
    }
  }, [page, perPage]);

Step 7: Search Handler

  const [searchEntities, searchResult] = useSearchMyEntitiesEntriesMutation({});
  const [searchDisabled, setSearchIsDisabled] = useState(false);

  const submitSearchValue = () => {
    setSearchIsDisabled(true);
    setIsSearchActive(true);

    searchEntities({
      searchValue,
      sizeLimit: 0,
      apiVersion: apiVersion || API_VERSION_BACKUP,
      startIdx: firstIdx,
      stopIdx: lastIdx,
    }).then((result) => {
      if ("data" in result) {
        const searchError = result.data?.error;

        if (searchError) {
          dispatch(addAlert({
            name: "submit-search-value-error",
            title: searchError.message || "Error when searching",
            variant: "danger",
          }));
          setIsSearchActive(false);
          setSearchData(null);
        } else {
          const results = result.data?.result.results || [];
          const searchTotalCount = result.data?.result.totalCount || 0;
          const entities: MyEntity[] = [];
          for (let i = 0; i < results.length; i++) {
            entities.push(results[i].result);
          }
          setSearchData({
            elementsList: entities,
            totalCount: searchTotalCount,
          });
        }
        setSearchIsDisabled(false);
      }
    });
  };

Legacy Pattern (Avoid)

The older pattern using useEffect + setState triggers eslint warnings:

// AVOID: This pattern causes @eslint-react/hooks-extra/no-direct-set-state-in-use-effect warnings
useEffect(() => {
  if (dataResponse.isSuccess && batchResponse) {
    setEntitiesList(/* ... */);  // Warning!
    setTotalCount(/* ... */);    // Warning!
    setShowTableRows(true);      // Warning!
  }
}, [dataResponse]);

Use the useMemo pattern from Step 4 instead.