Cache and Load Status
This approach prevents the store from fetching data that is already available. Essentially, it introduces a caching layer to your state management.
Implementing this pattern involves two key steps. First, we add a new property, loadStatus, to the state. The state then uses this property internally to decide whether a call to the backend is necessary.
Standard state management guides typically rely on two actions—load and loaded—to handle endpoint requests. Our pattern introduces a third action, named get. Components should exclusively use the get action. The load action becomes an internal mechanism, used only within the state management layer itself.
The following diagram illustrates the typical sequence of actions, effects, and reducers when loading data into an initially empty state.
Once data is present in the state, components can dispatch the get action as frequently as needed. This will not trigger any superfluous requests, as shown here:
Example Walkthrough
In our sample application, we have a component for displaying a list of customers and a separate component for showing a detailed edit form. Both components require the customer data and must ensure it is loaded before they can operate.
You might argue that a user always navigates from the list to the detail view, making it sufficient for only the list component to trigger the data load. However, we cannot depend on this assumption. Users might access the form directly via a deep link, or other parts of the application might link to it directly.
This creates a challenge: if we rely on each component to dispatch the load action, simply browsing through the user list could generate a large number of redundant endpoint requests.
To address this, we introduce a loadStatus property. The data in the store can exist in one of three states: not loaded, currently loading, or fully loaded. Additionally, we want our components to render only when the data is available.
LoadStatus is defined as a union type with these three possible values. The state includes this property, and its initial value is set to “NOT_LOADED”. The state definition evolves from:
to:
We then introduce a new action called get. Components will only interact with this action. Unlike load, the get action informs the store that data is required, without directly initiating a request.
An effect is set up to handle the get action. This effect examines the current state; if the state is not “LOADED”, it dispatches the actual load action. It's important to note that the load action is now considered an “internal” action and should never be dispatched by components or services.
In addition to the effect for the load action, we add a reducer that sets loadStatus to “LOADING”. This provides a significant advantage: it ensures that duplicate or parallel requests cannot occur. This is guaranteed by the design itself.
The final adjustment involves modifying our selectors. They should only emit data when loadStatus equals "LOADED". As a result, components will only render once the data is fully received.
Additional Points
One might wonder why we don't simply use a null value to indicate that data hasn't been loaded. The problem is that as consumers of the state, we cannot always be certain about the initial value—it might be null from the backend, or it could be another default value. Having an explicit loadStatus removes this ambiguity.
This reasoning also applies to arrays. Does an empty array signify that the store is freshly initialized, or does it genuinely mean there is no data? We wouldn't want to display a “No data found” message when, in fact, a request is still waiting for a response.
For More Complex Scenarios
In intricate UIs, the store might receive numerous actions in rapid succession. For example, when several components dispatch load actions simultaneously, these actions collectively build up a state that another component wants to display. A similar situation could involve a sequence of dependent actions. In such cases, a component might need to wait until the final action completes before rendering.
Without the loadStatus property, a selector could emit intermediate state changes, leading to an unpleasant flickering effect in the UI. To prevent this, selectors should check loadStatus first before returning the actual data. This approach ensures the component receives the data exactly once, at the right moment— which is both efficient and performant.



