Skip to content

Commit

Permalink
chore: apply middleware enhancer
Browse files Browse the repository at this point in the history
  • Loading branch information
iamogbz committed Sep 12, 2020
1 parent fae845a commit 20cc271
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 6 deletions.
8 changes: 6 additions & 2 deletions src/components/Provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export function Provider<S, T extends string, P>({
const root = React.useContext(Context);

const [state, reducerDispatch] = React.useReducer(root.reducer, root.state);
const stateRef = React.useRef(state);
const getState = React.useCallback(function getState() {
return stateRef.current;
}, []);

const dispatch = React.useCallback<ContextValue<S, T, P>["dispatch"]>(
function wrappedDispatch(action) {
Expand All @@ -19,8 +23,8 @@ export function Provider<S, T extends string, P>({
);

const enhanced = React.useMemo<ContextValue<S, T, P>>(
() => root.enhance({ ...root, dispatch }),
[root, dispatch],
() => root.enhance({ ...root, dispatch, getState }),
[root, dispatch, getState],
);

React.useEffect(
Expand Down
1 change: 1 addition & 0 deletions src/createContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export function createContext<S, T extends string, P>(
const Context = React.createContext<ContextValue<S, T, P>>({
dispatch: idFn,
enhance: idFn,
getState: () => preloadedState,
reducer: rootReducer,
state: preloadedState,
[SymbolObservable]: unimplemented(SymbolObservable.toString()),
Expand Down
32 changes: 32 additions & 0 deletions src/utils/applyMiddleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// github.com/reduxjs/redux/blob/8551ba8/src/applyMiddleware.ts
import { compose } from "./compose";

export function applyMiddleware<S, T extends string, P>(
...middlewares: Middleware<S, T, P>[]
): ContextEnhance<S, T, P> {
return function enhance(
context: ContextValue<S, T, P>,
): ContextValue<S, T, P> {
const dispatchStub: ContextDispatch<T, P> = () => {
throw new Error(
"Dispatching while constructing your middleware is not allowed. " +
"Other middleware would not be applied to this dispatch.",
);
};

const middlewareAPI: MiddlewareAPI<S, T, P> = {
getState: context.getState,
dispatch: (action, ...args) => dispatchStub(action, ...args),
};
const chain = middlewares.map((middleware) =>
middleware(middlewareAPI),
);
const dispatch = compose<typeof context.dispatch>(...chain)(
context.dispatch,
);
return {
...context,
dispatch,
};
};
}
60 changes: 60 additions & 0 deletions src/utils/compose.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// github.com/reduxjs/redux/blob/f1fc7ce/src/compose.ts
type Func<T extends unknown[], R> = (...a: T) => R;

/**
* Composes single-argument functions from right to left. The rightmost
* function can take multiple arguments as it provides the signature for the
* resulting composite function.
*
* @param funcs The functions to compose.
* @returns A function obtained by composing the argument functions from right
* to left. For example, `compose(f, g, h)` is identical to doing
* `(...args) => f(g(h(...args)))`.
*/
export function compose(): <R>(a: R) => R;

export function compose<F extends Function>(f: F): F;

/* two functions */
export function compose<A, T extends unknown[], R>(
f1: (a: A) => R,
f2: Func<T, A>,
): Func<T, R>;

/* three functions */
export function compose<A, B, T extends unknown[], R>(
f1: (b: B) => R,
f2: (a: A) => B,
f3: Func<T, A>,
): Func<T, R>;

/* four functions */
export function compose<A, B, C, T extends unknown[], R>(
f1: (c: C) => R,
f2: (b: B) => C,
f3: (a: A) => B,
f4: Func<T, A>,
): Func<T, R>;

/* rest */
export function compose<R>(
f1: (a: unknown) => R,
...funcs: Function[]
): (...args: unknown[]) => R;

export function compose<R>(...funcs: Function[]): (...args: unknown[]) => R;

export function compose(...funcs: Function[]): Function {
if (funcs.length === 0) {
// infer the argument type so it is usable in inference down the line
return <T>(arg: T): T => arg;
}

if (funcs.length === 1) {
return funcs[0];
}

return funcs.reduce(
(a, b) => ((...args: unknown[]) => a(b(...args))) as typeof a,
);
}
1 change: 1 addition & 0 deletions tests/__snapshots__/createContext.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ exports[`createContext has unimplemented observable symbol 1`] = `
Object {
"dispatch": [Function],
"enhance": [Function],
"getState": [Function],
"reducer": [Function],
"state": Object {},
Symbol(@@observable): [Function],
Expand Down
9 changes: 5 additions & 4 deletions typings/context.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ type ContextDispatch<T extends string = string, P = unknown> = (
action: Action<T, P>,
) => Action<T, P>;

type ContextEnhance<V extends ContextValue> = (value: V) => V;
type ContextEnhance<S = unknown, T extends string = string, P = unknown> = (
value: ContextValue<S, T, P>,
) => ContextValue<S, T, P>;

// this is similar to a redux store
type ContextValue<S = unknown, T extends string = string, P = unknown> = {
dispatch: ContextDispatch<T, P>;
enhance: ContextEnhance<ContextValue<S, T, P>>;
enhance: ContextEnhance<S, T, P>;
reducer: Reducer<S, T, P>;
state: S;
};
} & MiddlewareAPI<S, T, P>;

type Context<
S = unknown,
Expand Down
8 changes: 8 additions & 0 deletions typings/middleware.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
type MiddlewareAPI<S = unknown, T extends string = string, P = unknown> = {
dispatch: ContextDispatch<T, P>;
getState: () => S;
};

type Middleware<S = unknown, T extends string = string, P = unknown> = (
api: MiddlewareAPI<S, T, P>,
) => (next: ContextDispatch<T, P>) => ContextDispatch<T, P>;

0 comments on commit 20cc271

Please sign in to comment.