T8 React Pending

Example

Objective: Track the pending state of the async fetchItems() action to tell the user whether the UI is busy or encountered an error (preferably without rewriting the action and the app's state management).

+ import { usePendingState } from "@t8/react-pending";

  export const ItemList = () => {
    const [items, setItems] = useState([]);
+   const [state, withState] = usePendingState("fetch-items");

    useEffect(() => {
-     fetchItems().then(setItems);
+     withState(fetchItems()).then(setItems);
    }, [fetchItems, withState]);

+   if (!state.complete) return <p>Loading...</p>;
+   if (state.error) return <p>An error occurred</p>;

    return <ul>{items.map(/* ... */)}</ul>;
  };
+ import { usePendingState } from "@t8/react-pending";

  export const Status = () => {
+   const [state] = usePendingState("fetch-items");

    if (!state.initialized) return "";
    if (!state.complete) return "Busy";
    if (state.error) return "Error";

    return "OK";
  };

Live demo

🔹 In this example, the action's value (the items array) is stored in the component's local state, but it can be stored in any app state of the developer's choice.

Shared and local pending state

Omit the custom string key parameter of usePendingState() to scope the pending state locally within a single component:

- const [state, withState] = usePendingState("fetch-items"); // shared
+ const [state, withState] = usePendingState(); // local

Silent tracking of background and optimistic updates

- withState(fetchItems())
+ withState(fetchItems(), { silent: true })

🔹 This option prevents state.complete from switching to false in the pending state.

Delayed pending state

- withState(fetchItems())
+ withState(fetchItems(), { delay: 500 })

🔹 Use case: avoiding flashing a process indicator when the action is likely to complete by the end of a short delay.

Custom rejection handler

- withState(fetchItems())
+ withState(fetchItems(), { throws: true }).catch(handleError)

🔹 This option allows the async action to reject explicitly, along with exposing state.error that goes by default.

Providing blank initial pending state

+ import { PendingStateProvider } from "@t8/react-pending";

- <App/>
+ <PendingStateProvider>
+   <App/>
+ </PendingStateProvider>

🔹 <PendingStateProvider> creates an isolated instance of initial shared action state. Prime use cases: tests, SSR. It isn't required with client-side rendering, but it can be used to separate action states of larger self-contained portions of a web app.

Providing custom initial pending state

+ const initialState = {
+   "fetch-items": { initialized: true, complete: true },
+ };

- <PendingStateProvider>
+ <PendingStateProvider value={initialState}>
    <App/>
  </PendingStateProvider>

🔹 While fully optional, this setup allows to override the initial state received from usePendingState(stateKey).

🔹 With an explicit value or without, the <PendingStateProvider>'s nested components will only respond to updates in the particular action states they subscribed to by means of usePendingState(stateKey).