C3 AI Documentation Home

Fetch and share data using application state

Use application state when multiple components need to access the same data. This pattern reduces duplication and avoids redundant fetches. The platform uses Redux to manage shared frontend state.

This topic builds on the WindTurbine Type described in Fetch data with useC3Action. If you haven't set up that Type yet, start there first.

Step 1: Define the fetch action

Define a Redux-style action that tells the epic to begin the fetch operation. This action acts as the trigger for the rest of the data-fetching workflow.

TypeScript
// {package}/src/state/WindTurbine/actions.ts

export function fetchWindTurbinesAction(stateId: string): UiSdlReduxAction {
  return {
    type: `${stateId}.FETCH_TURBINES`, // Include the state ID in the action
    payload: { stateId }               // Pass the state context
  };
}

This function returns a Redux action object that follows a namespaced convention using the stateId. The type field ensures the action matches the pattern that the epic listens for. The payload provides contextual metadata (in this case, the stateId) that the epic and reducer needs later to route and store the result correctly.

This action acts as the entry point for the Redux-based fetch flow. Dispatching this action triggers the epic to perform the actual fetch and eventually pass the result to a reducer that persists it in application state.

Step 2: Define a function that sends the request

This function uses the native fetch API to send a POST request to the backend.

TypeScript
// {package}/src/state/WindTurbine/api.ts

export async function fetchTurbinesFromBackend(status: number) {
  const response = await fetch('/api/WindTurbine/getWindTurbineByStatus', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ status })
  });

  if (!response.ok) {
    throw new Error(`Fetch failed with status ${response.status}`);
  }

  return await response.json();
}

This utility serves as the async request layer in the epic. It does not manage application state or dispatch actions. Its only job is to handle the network call and return data for further processing.

Step 3: Create an epic that fetches and dispatches

This epic listens for the fetch action, calls the fetch function, and dispatches the result.

fetchWindTurbinesEpic defines an epic that reacts to the ${stateId}.FETCH_TURBINES action. It uses rxjs operators to handle the async workflow:

That action carries both the stateId and the fetched data in the payload

The epic does not store data itself. It serves as a middleware function that bridges between the original fetch trigger and the reducer responsible for persisting the result. This keeps side effects out of reducers and centralizes data-fetching logic in a consistent, declarative way.

TypeScript
// {package}/src/state/WindTurbine/epics.ts

import { from, of } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
import { fetchTurbinesFromBackend } from './api';

export function fetchWindTurbinesEpic(actionStream, _stateStream) {
  return actionStream.pipe(
    mergeMap((action) => {
      return from(fetchTurbinesFromBackend(1)).pipe(
        mergeMap((data) => {
          return of({
            type: `${action.payload.stateId}.STORE_TURBINES`,
            payload: {
              stateId: action.payload.stateId,
              data
            }
          });
        })
      );
    })
  );
}

Step 4: Store the result

Use a reducer to write the result into the Redux state tree.

TypeScript
// {package}/src/state/WindTurbine/reducers.ts

import { setConfigInApplicationState } from '@c3/ui/UiSdlApplicationState';

export function windTurbinesStoreReducer(state, action) {
  return setConfigInApplicationState(
    action.payload.stateId,
    state,
    ['turbines'],
    action.payload.data
  );
}

Step 5: Read data in any component

Use getConfigFromApplicationState to read the shared turbine data from Redux.

TypeScript
// {package}/ui/c3/src/customInstances/WindTurbine.ReadFromAppState.tsx

import { getConfigFromApplicationState } from '@c3/ui/UiSdlApplicationState';

const turbines = getConfigFromApplicationState(
  'WindTurbine.AppState',
  reduxState,
  ['turbines']
);

Use the Redux setup in your application

Now that you've defined the Redux logic across separate files, wire everything together using a single registration file. This central registration makes the epic and reducer easier to import into your app's global state configuration and ensures consistency wherever the turbine data is used.

The following file declares the state ID, associates the reducer with that ID, and exports the epic for inclusion.

TypeScript
// {package}/src/state/WindTurbine/registerWindTurbinesState.ts

import { fetchWindTurbinesEpic } from './epics';
import { windTurbinesStoreReducer } from './reducers';

export const windTurbineStateId = 'WindTurbine.AppState';

export const windTurbineReducerEntry = {
  [windTurbineStateId]: windTurbinesStoreReducer
};

export const windTurbineEpics = [fetchWindTurbinesEpic];

Add the reducer to your global state setup

Update your root reducer configuration so Redux knows how to store and access the turbine data. The example below adds your turbine reducer entry to the rootReducers object.

TypeScript
// {package}/src/state/rootReducers.ts
import { windTurbineReducerEntry } from './WindTurbine/registerWindTurbinesState';

export const rootReducers = {
  ...windTurbineReducerEntry,
  // other reducers
};

Register the epic in your global epic array

Update your root epic configuration to include the turbine-related epic. The epic listens for fetch actions and performs the backend call.

TypeScript
// {package}/src/state/rootEpics.ts
import { windTurbineEpics } from './WindTurbine/registerWindTurbinesState';

export const rootEpics = [
  ...windTurbineEpics,
  // other epics
];

Dispatch and read data in a component

Use the fetchWindTurbinesAction function to trigger the backend fetch. Pass the state ID from your registration file so the platform stores the result correctly. This example dispatches the fetch action on component render or inside an event handler.

TypeScript
// Trigger the fetch
import { useDispatch } from 'react-redux';
import { fetchWindTurbinesAction } from '../../state/WindTurbine/actions';
import { windTurbineStateId } from '../../state/WindTurbine/registerWindTurbinesState';

const dispatch = useDispatch();
dispatch(fetchWindTurbinesAction(windTurbineStateId));

Once the epic completes and the reducer stores the data, use getConfigFromApplicationState to read it inside any React component. Provide the state ID and path to access the turbine array.

TypeScript
// Read the data
import { getConfigFromApplicationState } from '@c3/ui/UiSdlApplicationState';
import { windTurbineStateId } from '../../state/WindTurbine/registerWindTurbinesState';

const turbines = getConfigFromApplicationState(
  windTurbineStateId,
  reduxState,
  ['turbines']
);

Use application state when multiple components rely on the same dataset. This approach lets you fetch data once, store it in Redux, and access it from anywhere in the UI. It works well for shared grids, charts, or dashboards where consistency matters across views.

Was this page helpful?