Managing State
Component State
Most state management in the app is done by components. Some state is entirely internal to a component, other state is drilled down to child components. The mechanism for this kind of state management depends on the type of component.
Function Components
Two options are available for component state in the newer function components:
useState
Most function components use the useState
hook to manage component state. A few guidelines:
- Use a separate instance of
useState
for each piece of state. useState
can infer the type based on the initial value, or can be explicitly typed.- Inferred:
const [count, setCount] = React.useState(0)
- Explicit:
const [data, setData] = React.useState<number | null>(null);
- Inferred:
- When referencing the current value during state updates, use the function updater pattern:
setMyState(currentVal => currentVal + 1)
.
useReducer
If a component's state is complex, you may consider using useReducer
. This method requires much more boilerplate, so thoroughly consider the tradeoffs before going down this path. Some tips:
- Actions can be typed using the union operator:
type ReducerAction = { type: 'ADD', payload: number } | { type: 'SUBTRACT', payload: number };
- Consider using the
immer
package to reduce boilerplate in the reducer.
Class Components
Class components use the traditional React this.state
and this.setState
patterns.
- The state shape is given as a type parameter:
class MyComponent extends React.Component<Props, State>
. - The default state is defined as a class variable:
state: State = { ... }
. - When referencing the current state in an update, use the function updater pattern:
this.setState(prevState => ({ count: prevState.count + 1 }))
Global State
Two options are currently in use for managing state that many different components care about: Redux and Context.
Redux
Historically Redux was use heavily for state management and data storage, including almost all data fetched from the API.
Currently, some API data is still stored in Redux, but data fetching in general is being migrated to React Query. Redux is still used for some global state.
- Redux code lives in
src/store
. - Each slice of state has its own directory in
src/store
which includes actions and the reducer. - Reducers are combined via
combineReducers
insrc/store/index
. - Components consume Redux state by using the standard react-redux
connect
HOC, though it's common to write a custom, reusable container HOC insrc/containers
for each state slice in the store.
Context
This is a new pattern that takes advantage of React's useContext
hook. This pattern has not been used widely in the app, and as such best/common practices are not available.
The general idea is to create a file in src/context
for a specific slice of context. The context Provider
is included at a high-level (like MainContent
) and consumers can use useContext
to read and update the state.