Open-Source Wikis

/

Grafana

/

Frontend

/

State management

grafana/grafana

State management

Grafana's frontend uses two complementary tools:

  • Redux Toolkit for client UI state (slices).
  • RTK Query for server state (data fetching & caching).

A small amount of legacy state still uses connect-style reducers and a global event bus, but new code uses the patterns described here.

Slices

@reduxjs/toolkit slices replace classic action-creators + reducers. Each feature owns its own slice:

public/app/features/<feature>/state/
├── reducers.ts            # createSlice({ name, initialState, reducers, extraReducers })
├── actions.ts             # createAsyncThunk(...)
├── selectors.ts           # Memoized selectors via reselect
└── types.ts

The store config is in public/app/store/configureStore.ts and the root reducer in public/app/core/reducers/root.ts.

RTK Query

Most data fetching uses RTK Query. A typical feature exposes one API instance:

// public/app/features/dashboard/api/dashboardApi.ts
export const dashboardApi = createApi({
  reducerPath: 'dashboardApi',
  baseQuery: createBackendSrvBaseQuery({ baseURL: '/api' }),
  tagTypes: ['Dashboard'],
  endpoints: (builder) => ({
    getDashboard: builder.query<Dashboard, string>({
      query: (uid) => `/dashboards/uid/${uid}`,
      providesTags: ['Dashboard'],
    }),
    saveDashboard: builder.mutation<SaveResponse, SaveCommand>({
      query: (cmd) => ({ url: '/dashboards/db', method: 'POST', body: cmd }),
      invalidatesTags: ['Dashboard'],
    }),
  }),
});

createBackendSrvBaseQuery is a thin wrapper over getBackendSrv().fetch(...) that ensures auth, CSRF, and error handling are consistent.

Each createApi call exports auto-generated hooks (useGetDashboardQuery, useSaveDashboardMutation) that components consume.

Generated API clients

For app-platform / Kubernetes-style endpoints, RTK Query slices are auto-generated from the OpenAPI spec via packages/grafana-api-clients/ and yarn generate-apis. The generated clients live under packages/grafana-api-clients/src/api/<resource>.gen.ts and are wrapped by feature-specific endpoints when needed.

Where state lives

Kind of state Where
Server cache (lists, detail pages) RTK Query API in <feature>/api/<feature>Api.ts
Local UI state (modal open?, form draft) Slice in <feature>/state/reducers.ts
Cross-feature shared state (navTree, theme, breadcrumbs, app notifications) public/app/core/reducers/
Component-local state useState / useReducer
Page-scoped state (dashboard model) The dashboard-scene runtime (SceneObjects)

Selectors

Selectors are co-located with the slice. For derived data, use reselect createSelector to memoize. The custom-typed useSelector hook lives in public/app/types/store.ts and propagates the root-state type.

Async patterns

  • For "I just need to load data on mount" → RTK Query's useQuery hook.
  • For "I want to dispatch an explicit action that does some work" → createAsyncThunk.
  • For "I want to subscribe to a stream" → use the runtime.live channel API or RxJS observables; persist results into a slice if needed.

DevTools

Redux DevTools are wired in development. The store is named grafana and shows up in the extension. Action types are namespaced by slice (dashboards/setSearchQuery, etc.).

Anti-patterns to avoid

  • Adding to the global root reducer for one-feature state. Use a local slice.
  • Storing server data in slices when an RTK Query endpoint would do.
  • getState() calls inside React components; use selectors and hooks instead.
  • New connect() HOCs; use useSelector/useDispatch (or RTK Query hooks).

See also

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

State management – Grafana wiki | Factory