Frontend State Management
Relevant source files
The following files were used as context for generating this wiki page:
- frontend/src/contexts/AuthenticationContext.tsx
- frontend/src/api/collimator/hooks/helpers.ts
- frontend/src/components/MultiSwrContent.tsx
- frontend/package.json
- frontend/src/pages/task/[taskId]/edit.tsx
- frontend/src/pages/login/oidc-redirect.tsx
- frontend/src/components/session/SessionForm.tsx
- frontend/src/pages/class/[classId]/session/[sessionId]/task/[taskId]/solve.tsx
- frontend/src/components/dashboard/ProgressList.tsx
The frontend application, built with Next.js, employs a multi-layered strategy for state management, separating concerns between server state, global application state, and local component/form state. This approach leverages specialized libraries to handle different types of state efficiently.
The primary library for managing server state is swr, which handles data fetching, caching, and revalidation. For global state, particularly user authentication, React's Context API is utilized. For complex local state within forms, react-hook-form is used in conjunction with yup for validation. Simple UI state is managed using standard React hooks like useState and useCallback.
Core Technologies
The state management architecture is built upon several key libraries.
| Library | Role |
|---|---|
swr | Handles server-side state: data fetching, caching, revalidation, and mutation. |
react | Provides useState, useCallback, useContext, etc., for local and global state. |
react-hook-form | Manages form state, including values, validation, and submission state. |
yup | Provides schema-based validation for react-hook-form. |
openid-client | Used on the server-side, but its effects are handled in the frontend for OIDC authentication flow. |
Sources: frontend/package.json:80-91
Server State Management with SWR
Server state, which includes all data fetched from the backend API, is managed exclusively by swr. The application abstracts swr's core functionality into custom hooks, creating a consistent and reusable data layer.
Custom SWR Hooks
Custom hooks are the primary interface for components to interact with the backend API. These hooks encapsulate the logic for fetching (useSWR), mutating (useSWRMutation), and caching data.
A common pattern is to create a dedicated hook for each API resource or endpoint. For example, useTask fetches a single task, and useUpdateTask provides a function to update a task.
This diagram shows the flow of creating and using a custom SWR hook for data mutation.
Handling Multiple Data Dependencies
Many pages need to fetch data from multiple endpoints concurrently. To simplify the handling of their collective loading and error states, the MultiSwrContent component is used. It takes arrays of data, loading flags, and errors from multiple SWR hooks and only renders its children when all data has been successfully fetched.
The logic of MultiSwrContent is as follows:
An example of its usage can be seen in the EditTask page, which fetches both task details and the task file.
// frontend/src/pages/task/[taskId]/edit.tsx:26-28
const task = useTaskWithReferenceSolutions(taskId);
const taskFile = useTaskFile(taskId);
const updateTask = useUpdateTask();
// frontend/src/pages/task/[taskId]/edit.tsx:45-50
<MultiSwrContent
data={[task.data, taskFile.data]}
isLoading={[task.isLoading, taskFile.isLoading]}
errors={[task.error, taskFile.error]}
>
{([task, taskFile]) => {
// ... render form
}}
</MultiSwrContent>
Sources: frontend/src/pages/task/[taskId]/edit.tsx:26-52, frontend/src/components/dashboard/ProgressList.tsx:199-203
Global State: Authentication
Global application state, specifically user authentication status, is managed via React's Context API.
AuthenticationContext
The AuthenticationContext provides information about the current user's authentication state throughout the component tree. An UpdateAuthenticationContext is also provided to allow components to modify this state.
The authentication state is defined by the AuthenticationContextType:
| Field | Type | Description |
|---|---|---|
version | number | The version of the context structure. |
isAnonymous | boolean | True if the user is a student without a named account. |
idToken | string | undefined | The OIDC ID token. |
authenticationToken | string | undefined | The JWT used for authenticating with the Collimator backend. |
role | UserRole | undefined | The role of the user (e.g., student, teacher, admin). |
name | string | undefined | The user's name. |
Sources: frontend/src/contexts/AuthenticationContext.tsx:17-31
Authentication Flow
The oidc-redirect page handles the OpenID Connect callback. It uses the authenticate utility, receives tokens, and then updates the global AuthenticationContext.
This sequence diagram illustrates the process:
Local and Form State
State that is local to a component or a form is managed using either standard React hooks or react-hook-form.
Form State with react-hook-form
For forms, react-hook-form is the standard. It handles form values, validation, and submission state. Validation is performed using yup schemas.
The SessionForm component demonstrates this pattern:
- A
yupschema defines the validation rules. useFormfromreact-hook-formis initialized with the schema resolver.- Form inputs are registered with the hook.
handleSubmitwraps the submission logic, ensuring validation is run first.
UI State with React Hooks
Simple UI state, such as the visibility of a menu or a modal, is managed with useState. Callbacks are often memoized with useCallback to prevent unnecessary re-renders.
For instance, in the SolveTaskPage, the visibility of the session menu is controlled by a showSessionMenu state variable.
// frontend/src/pages/class/[classId]/session/[sessionId]/task/[taskId]/solve.tsx:68
const [showSessionMenu, setShowSessionMenu] = useState(false);
// frontend/src/pages/class/[classId]/session/[sessionId]/task/[taskId]/solve.tsx:130-132
const toggleSessionMenu = useCallback(() => {
setShowSessionMenu((show) => !show);
}, []);
Sources: frontend/src/pages/class/[classId]/session/[sessionId]/task/[taskId]/solve.tsx:68-69, 130-132
Summary
The Collimator frontend uses a well-defined, layered approach to state management. By using swr for server state, React Context for global authentication, and react-hook-form for forms, the application maintains a clear separation of concerns. This architecture promotes code that is scalable, maintainable, and performant by leveraging the strengths of each library for its intended purpose.