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.tsThe 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
useQueryhook. - For "I want to dispatch an explicit action that does some work" →
createAsyncThunk. - For "I want to subscribe to a stream" → use the
runtime.livechannel 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; useuseSelector/useDispatch(or RTK Query hooks).
See also
- Bootstrap and runtime — store creation.
- Core — cross-cutting reducers (navTree, app notifications).
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.