import { MutableRefObject, useCallback, useRef, useState } from 'react'; import useUpdateEffect from './useUpdateEffect'; type Dispatch = (action: Action) => void; interface Store { getState: () => State; dispatch: Dispatch; } type Middleware = (store: Store) => (next: Dispatch) => (action: Action) => void; function composeMiddleware(chain: Middleware[]) { return (context: Store, dispatch: Dispatch) => { return chain.reduceRight((res, middleware) => { return middleware(context)(res); }, dispatch); }; } const createReducer = (...middlewares: Middleware[]) => { const composedMiddleware = composeMiddleware(middlewares); return ( reducer: (state: State, action: Action) => State, initialState: State, initializer = (value: State) => value ): [State, Dispatch] => { const ref = useRef(initializer(initialState)); const [, setState] = useState(ref.current); const dispatch = useCallback( action => { ref.current = reducer(ref.current, action); setState(ref.current); return action; }, [reducer] ); const dispatchRef: MutableRefObject> = useRef( composedMiddleware( { getState: () => ref.current, dispatch: (...args: [Action]) => dispatchRef.current(...args), }, dispatch ) ); useUpdateEffect(() => { dispatchRef.current = composedMiddleware( { getState: () => ref.current, dispatch: (...args: [Action]) => dispatchRef.current(...args), }, dispatch ); }, [dispatch]); return [ref.current, dispatchRef.current]; }; }; export default createReducer;