06. State Management (Redux + Sagas)¶
This reference documents the Redux store wiring, modules pattern (createSlice utils), sagas, and selectors with direct code links.
Store¶
- Configure store + Saga middleware:
src/redux/index.js:1,src/redux/index.js:5,src/redux/index.js:6,src/redux/index.js:10,src/redux/index.js:38,src/redux/index.js:42 - Root reducer (combined from module reducers):
src/redux/reducers.js:1,src/redux/reducers.js:5 - Root saga (composed from all slice sagas):
src/redux/modules/index.js:27,src/redux/modules/index.js:32 - Registered slices in root tree:
src/redux/modules/index.js:16
Modules Pattern¶
Each feature module is a self-contained slice: actions, reducers, sagas, selectors.
- Projects: src/redux/modules/projects/index.js:25 (slice), selectors :163, actions :28-69, sagas :72
- Processes: src/redux/modules/processes/index.js:25 (slice), selectors :349, actions :29-41,42-46,98-107, sagas :110
- Jobs: src/redux/modules/jobs/index.js:42 (slice), selectors :399, actions :47-127, sagas :323,340,357,373
- Tasks: src/redux/modules/tasks/index.js:136 (slice), selectors appear after reducers; actions :140-155 (many), reducers e.g. :157-220
- Files: src/redux/modules/files/index.js:24 (slice), selectors near end; actions :28-31, sagas :71
- Resources: src/redux/modules/resources/index.js:37 (slice), selectors :145, actions :41-44, sagas :79
- OMERO: src/redux/modules/omero/index.js:26 (slice), selectors and sagas implement OMERO data fetching
- Auth (users): src/redux/modules/users/auth.js:22 (slice), selectors :75, sagas :56
Notes
- Common initial shape: isFetching, error, and domain data (e.g., projects, processes, jobs).
- Success reducers call stopFetching; request starters use startFetching.
createSlice Utils¶
Custom wrapper extends RTK’s createSlice and wires sagas + cancellation.
- Action wrapper with optional namespace: src/redux/utils/createSlice.js:6-25, type for cancel with token: :4
- Default reducers added: cancel and clear(initialState): src/redux/utils/createSlice.js:187-191
- Selectors factory injection (getState default): src/redux/utils/createSlice.js:198-208
- Saga mapping per action with cancellation via race(take(cancel)): src/redux/utils/createSlice.js:219-261
Fetching helpers
- startFetching/stopFetching set isFetching and maintain _requests counter: src/redux/utils/index.js:9, src/redux/utils/index.js:19
- fail helper to set error: src/redux/utils/index.js:43
Sagas¶
Pattern: declare per-action saga in slice factory; utils attach watchers automatically.
- Root composition runs all slice watchers: src/redux/modules/index.js:27-34
- Example (Projects): list fetch flow in saga: src/redux/modules/projects/index.js:72-96
- Auth login flow: src/redux/modules/users/auth.js:56-72
- Jobs downloads with blob handling: src/redux/modules/jobs/index.js:373-395
- Processes create/delete with follow-up fetches and cross-module actions: src/redux/modules/processes/index.js:178-209, :212-226
Cancellation & Namespacing
- Dispatch namespaced actions via actions.someAction(payload, namespace); cancel with actions.cancel(namespace); utils derive cancelType_namespace: src/redux/utils/createSlice.js:4, :236-256
Selectors¶
Selectors are generated per slice via factory with provided getState.
- Auth: isAuthenticated → src/redux/modules/users/auth.js:81-84
- Projects: getProjects, getProject(id) → src/redux/modules/projects/index.js:169-177
- Processes: getProcessesOfProject(), getProcess(projectId, processId) → src/redux/modules/processes/index.js:360-373
- Jobs: getJobs(), getJobsByProcessId(processId), getJob(id) → src/redux/modules/jobs/index.js:415-437, :430-433
- Files: files list + fileKeys[fileName] via slice state → src/redux/modules/files/index.js
- Resources: getResources(), getResource(id) → src/redux/modules/resources/index.js:151-159
API Client & Effects¶
- Backend client (Axios) with baseURL, auth header, 401 auto-logout, and per-request cancellation token:
src/middleware/backendClient.js:1-7,:21-41,:63-73,:88-105,:117-137
Usage¶
- Dispatch:
dispatch(projects.actions.fetchProjects()) - Cancel (namespaced):
dispatch(projects.actions.cancel('projectList')) - Select:
useSelector(projects.selectors.getProjects)
Status: Done