From 766ab2f4e9d6be79e9171d38b87adac4f96e332c Mon Sep 17 00:00:00 2001 From: Ward Oosterlijnck Date: Tue, 16 Apr 2019 19:26:01 +1000 Subject: [PATCH] createReducer for redux middleware, related #164 --- package.json | 2 + src/__stories__/createReducer.story.tsx | 56 +++++++++++++++++++++++++ src/createReducer.ts | 35 ++++++++++++++++ src/index.ts | 2 + yarn.lock | 17 ++++++++ 5 files changed, 112 insertions(+) create mode 100644 src/__stories__/createReducer.story.tsx create mode 100644 src/createReducer.ts diff --git a/package.json b/package.json index 9a24b26348..ad05cffdef 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,8 @@ "react-hooks-testing-library": "0.4.0", "react-spring": "6.1.10", "rebound": "0.1.0", + "redux-logger": "^3.0.6", + "redux-thunk": "^2.3.0", "rimraf": "2.6.3", "rxjs": "6.4.0", "semantic-release": "15.13.3", diff --git a/src/__stories__/createReducer.story.tsx b/src/__stories__/createReducer.story.tsx new file mode 100644 index 0000000000..1edaf91e1a --- /dev/null +++ b/src/__stories__/createReducer.story.tsx @@ -0,0 +1,56 @@ +import { storiesOf } from '@storybook/react'; +import * as React from 'react'; +import logger from 'redux-logger'; +import thunk from 'redux-thunk'; + +import { createReducer } from '..'; +// import ShowDocs from './util/ShowDocs'; + +const useThunkReducer = createReducer(thunk, logger); + +// React useReducer lazy initialization example: https://reactjs.org/docs/hooks-reference.html#lazy-initialization +function init(initialCount) { + return { count: initialCount }; +} + +function reducer(state, action) { + switch (action.type) { + case 'increment': + return { count: state.count + 1 }; + case 'decrement': + return { count: state.count - 1 }; + case 'reset': + return init(action.payload); + default: + throw new Error(); + } +} + +const Demo = ({ initialCount = 1 }) => { + // Action creator to increment count, wait a second and then reset + const addAndReset = React.useCallback(() => { + return dispatch => { + dispatch({ type: 'increment' }); + + setTimeout(() => { + dispatch({ type: 'reset', payload: initialCount }); + }, 1000); + }; + }, [initialCount]); + + const [state, dispatch] = useThunkReducer(reducer, initialCount, init); + + return ( +
+
{JSON.stringify(state, null, 2)}
+ + + + +
+ ); +}; + +storiesOf('State|createReducer', module) + // .add('Docs', () => ) + .add('Demo', () => ); diff --git a/src/createReducer.ts b/src/createReducer.ts new file mode 100644 index 0000000000..7ff5fd3b83 --- /dev/null +++ b/src/createReducer.ts @@ -0,0 +1,35 @@ +import { useReducer } from 'react'; + +function compose(...funcs) { + if (funcs.length === 0) { + return arg => arg; + } + + if (funcs.length === 1) { + return funcs[0]; + } + + return funcs.reduce((a, b) => (...args) => a(b(...args))); +} + +const createReducer = (...middlewares) => (...args) => { + const [state, dispatch] = useReducer(...args); + + let middlewareDispatch = () => { + throw new Error( + 'Dispatching while constructing your middleware is not allowed. ' + + 'Other middleware would not be applied to this dispatch.' + ); + }; + + const middlewareAPI = { + getState: () => state, + dispatch: (...args) => middlewareDispatch(...args), + }; + const chain = middlewares.map(middleware => middleware(middlewareAPI)); + middlewareDispatch = compose(...chain)(dispatch); + + return [state, middlewareDispatch]; +}; + +export default createReducer; diff --git a/src/index.ts b/src/index.ts index 3923f60cca..bd458171c1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import createMemo from './createMemo'; +import createReducer from './createReducer'; import useAsync from './useAsync'; import useAsyncFn from './useAsyncFn'; import useAsyncRetry from './useAsyncRetry'; @@ -72,6 +73,7 @@ import useWindowSize from './useWindowSize'; export { createMemo, + createReducer, useAsync, useAsyncFn, useAsyncRetry, diff --git a/yarn.lock b/yarn.lock index 95ca898a8f..d71ee0ff7d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5007,6 +5007,11 @@ dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= +deep-diff@^0.3.5: + version "0.3.8" + resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84" + integrity sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ= + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -11333,6 +11338,18 @@ redeyed@~2.1.0: dependencies: esprima "~4.0.0" +redux-logger@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf" + integrity sha1-91VZZvMJjzyIYExEnPC69XeCdL8= + dependencies: + deep-diff "^0.3.5" + +redux-thunk@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" + integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== + refractor@^2.4.1: version "2.7.0" resolved "https://registry.yarnpkg.com/refractor/-/refractor-2.7.0.tgz#3ed9a96a619e75326a429e644241dea51be070a3"