DEV Community

Sosuke Suzuki
Sosuke Suzuki

Posted on

We can use Redux without Redux.

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 to StoreContext.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)