All questions

Mini Redux

Premium

Mini Redux

Redux is a state container: one plain object holds the whole app state, the only way to change it is to dispatch an action through a pure reducer, and any number of listeners can subscribe to be told when it changes. You will build that core plus the two helpers every real Redux app leans on, combineReducers and applyMiddleware, exposed as one object miniRedux.

createStore(reducer, preloadedState?, enhancer?) returns a store. dispatch(action) computes the next state with reducer(state, action) and then synchronously runs every subscriber. subscribe(listener) returns a function that unsubscribes. combineReducers(map) splices many small reducers into one. applyMiddleware(...mws) is an enhancer that wraps dispatch in a chain of middleware.

Signature

type Action = { type: string; [key: string]: unknown };
type Reducer<S> = (state: S | undefined, action: Action) => S;
type Middleware = (api: { getState(): unknown; dispatch(a: Action): Action }) =>
  (next: (a: Action) => Action) => (action: Action) => Action;

const miniRedux: {
  createStore<S>(reducer: Reducer<S>, preloadedState?: S, enhancer?: Function): {
    getState(): S;
    dispatch(action: Action): Action;
    subscribe(listener: () => void): () => void; // returns unsubscribe
  };
  combineReducers(map: Record<string, Reducer<unknown>>): Reducer<Record<string, unknown>>;
  applyMiddleware(...mws: Middleware[]): Function; // an enhancer for createStore
};

Examples

const { createStore } = miniRedux;

const counter = (state = 0, action) =>
  action.type === 'INC' ? state + 1 : state;

const store = createStore(counter);
store.getState(); // 0 — the reducer's initial state

const unsubscribe = store.subscribe(() => console.log(store.getState()));
store.dispatch({ type: 'INC' }); // logs 1
store.dispatch({ type: 'INC' }); // logs 2
unsubscribe();
store.dispatch({ type: 'INC' }); // state is 3, but nothing logs
const { createStore, combineReducers, applyMiddleware } = miniRedux;

const logger = (store) => (next) => (action) => {
  const result = next(action);
  console.log(action.type, store.getState());
  return result;
};

const root = combineReducers({ count: counter });
const store = createStore(root, applyMiddleware(logger));

store.dispatch({ type: 'INC' }); // logs: INC { count: 1 }
store.getState(); // { count: 1 }

Notes

  • Synchronousdispatch runs the reducer and notifies every subscriber before it returns. There are no timers and nothing is async.
  • Pure reducers — a reducer never mutates its input; it returns the next state, or the same state for actions it ignores.
  • Same object when unchangedcombineReducers returns the exact same state object when no slice changed, so reference-equality consumers can skip work.
  • Middleware shape — every middleware is store => next => action => {}, composed right-to-left, and each one receives { getState, dispatch } where dispatch routes through the whole chain.
  • Do not worry about — action creators, replaceReducer, time-travel, or React bindings. The three functions above are the whole surface.

FAQ

Why does dispatch update the state before notifying subscribers?
Subscribers read the store with getState(), so if you notified them before replacing the state they would all see the old value and the UI would render stale data. Compute the next state first, then run every listener.
Why does combineReducers return the same state object when nothing changed?
Consumers decide whether to re-render by comparing references. If the combined reducer built a fresh object on every action, prev === next would never hold and everything would re-run. Returning the original object when every slice is referentially unchanged lets them skip the work.
What does it mean that middleware is composed right-to-left?
compose(A, B)(dispatch) becomes A(B(dispatch)), so the rightmost middleware wraps the base dispatch first. An action still enters the leftmost middleware first: A runs, calls next into B, B calls next into the reducer, then control unwinds back out through B and A.

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium