2022-07-13 15:20:04 +01:00
|
|
|
---
|
|
|
|
tags:
|
|
|
|
- javascript
|
|
|
|
- react
|
|
|
|
---
|
|
|
|
|
|
|
|
# Application state management
|
|
|
|
|
2024-06-15 11:30:03 +01:00
|
|
|
Although [React_useReducer](React_useReducer.md) and [React_useContext](React_useContext.md) have
|
2024-02-02 15:58:13 +00:00
|
|
|
many sound use cases by themselves, when they are combined they offer a way to
|
|
|
|
acheive a system of global state management, without utilising third-party
|
|
|
|
libraries like Redux.
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
|
|
## Requirements
|
|
|
|
|
|
|
|
The Context API combined with a reducer addresses the following needs:
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
- be able to consume global state from anywhere in the component hierarchy
|
|
|
|
without prop-drilling
|
|
|
|
- update global state from within child components from anywhere in the
|
|
|
|
component hierarchy
|
|
|
|
- access, maintain and update complex structured data (arrays/objects) not just
|
|
|
|
single values, as global state
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
|
|
## Implementation
|
|
|
|
|
|
|
|
In essence the approach is as follows:
|
|
|
|
|
|
|
|
- Create a context provider that houses the global state object
|
2024-02-02 15:58:13 +00:00
|
|
|
- Attach this state to a reducer function that operates on the state object
|
|
|
|
through dispatch methods
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
|
|
First off, our initial state and overall state object:
|
|
|
|
|
|
|
|
```js
|
|
|
|
const initialState = {
|
|
|
|
count: 0,
|
|
|
|
};
|
|
|
|
```
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
Next, we create our context. This is what we will invoke when we want to consume
|
|
|
|
our state.
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
|
|
```js
|
|
|
|
export const CounterContext = React.createContext({});
|
|
|
|
```
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
Now we need a reducer that will handle the state updates. We will just use the
|
|
|
|
same setup as we used in the example of
|
2024-06-15 11:30:03 +01:00
|
|
|
[React_useReducer](React_useReducer.md#refining-the-syntax):
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
|
|
```js
|
|
|
|
function reducer(state, action) {
|
|
|
|
switch (action.type) {
|
2024-02-02 15:58:13 +00:00
|
|
|
case "increase":
|
|
|
|
return { ...state, counter: state.counter + 1 };
|
2022-07-13 15:20:04 +01:00
|
|
|
break;
|
2024-02-02 15:58:13 +00:00
|
|
|
case "decrease":
|
|
|
|
return { ...state, counter: state.counter - 1 };
|
2022-07-13 15:20:04 +01:00
|
|
|
break;
|
2024-02-02 15:58:13 +00:00
|
|
|
case "increase_by_payload":
|
|
|
|
return { ...state, counter: state.counter + action.payload };
|
2022-07-13 15:20:04 +01:00
|
|
|
default:
|
|
|
|
throw new Error();
|
|
|
|
}
|
|
|
|
return newState;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
Finally, we need to make a provider as a top-level component that receives the
|
|
|
|
reducer's `state` and `dispatch` methods as props:
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
|
|
```js
|
|
|
|
export const CounterProvider = ({children}) => {
|
|
|
|
// We pass our reducer function and initial state to useReducer as params
|
|
|
|
const [state, dispatch] = React.useReducer(reducer, initialState)
|
|
|
|
|
|
|
|
// We create our provider and pass the reducer state and update method as props. This is the provider to the CounterContext consumer
|
|
|
|
<CounterContext.Provider value={{state, dispatch}}>
|
|
|
|
{children}
|
|
|
|
</CounterContext.Provider>
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
Now whenever we want to consume or update our global state, we can invoke the
|
|
|
|
context within a lower-level component, for example:
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
|
|
```js
|
|
|
|
const {state, dispatch} = useContext(CounterContext);
|
|
|
|
|
|
|
|
dispatch({
|
|
|
|
type 'increase_by_payload',
|
|
|
|
payload: 4
|
|
|
|
})
|
|
|
|
|
|
|
|
console.log(state.counter) // 4
|
|
|
|
|
|
|
|
```
|