We can manage state like Redux without Redux with addition new feature "Hooks".
useReducer + Context
- First, define the reducer like Redux.
- Second, create the Context that manages state and dispatch.
- Third, create state and reducer with
useReducer
and pass those toStoreContext.Provider
. - Finaly, access to state or dispatch with
useContext
(or,StoreContext.Consumer
).
import * as React from "react";
import * as ReactDOM from "react-dom";
type State = { count: number };
type Action = { type: "increment" | "decrement" | "reset" };
const initialState: State = { count: 0 };
function reducer(state: CounterState, action: Action): State {
case "reset": {
return initialState;
}
case "increment": {
return { count: state.count + 1 };
}
case "decrement": {
return { count: state.count - 1 };
}
default: {
return state;
}
}
const StoreContext = React.createContext<{
state: State,
dispatch: React.Dispatch<Action>
}>({state: null as any, dispatch: null as any});
function App() {
const [state, dispatch] = React.useReducer(reducer, initialState);
return (
<StoreContext value={{ state, dispatch }}>
<Counter />
</StoreContext>
)
}
function Counter() {
const { state, dispatch } = React.useContext(StoreContext);
return (
<>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: "reset" })}>reset</button>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</>
)
}
ReactDOM.render(<App />, document.querySelector("#root"));
I like this pattern! This is useful for small project. We should use Redux only we want to use middlewares.
reducer-context-hook
I published a library named reducer-context-hook
(GitHub: https://github.com/sosukesuzuki/reducer-context-hook ). This library wraps the above pattern.
reducer-context-hook
exports a function named create()
. create()
function returns a object contains StoreContextProvider
, useMappedState
and useDispatch
.
import create from "reducer-context-hook";
export const { StoreContextProvider, useDispatch, useMappedState } = create<
State,
Action
>();
StoreContextProvider
StoreContextProvider
has same role as StoreContext.Provider
in the above pattern. Please use like below:
function App() {
return (
<StoreContextProvider reducer={reducer} initialState={initialState}>
<Counter />
</StoreContextProvider>
);
}
useMappedState
useMappedState
has same role connect
in react-redux
. (only state). It recieves two arguments.The first is mapState
function.The second is memoizationArray
.memoizationArray
is used to memoize like useCallback
. (useCallback
beeing used in useMappedState
to memoize function.) Please use like below:
function Counter() {
const { count } = useMappedState(
state => ({
count: state.count
}),
[]
);
return <p>{count}</p>;
}
useDispatch
useDispatch
returns dispatch
.We can use action with it. Please use like below:
function Increment() {
const dispatch = useDispatch();
const increment = React.useCallback(
() => dispatch({ type: "increment" }),
[]
);
return <button onClick={increment}>+</button>;
}
Top comments (0)