diff --git a/package.json b/package.json
index d4cd3547..9efa84eb 100644
--- a/package.json
+++ b/package.json
@@ -90,6 +90,8 @@
"react-hooks-testing-library": "0.4.1",
"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.5.2",
"semantic-release": "15.13.12",
diff --git a/src/__stories__/createReducer.story.tsx b/src/__stories__/createReducer.story.tsx
new file mode 100644
index 00000000..65006db7
--- /dev/null
+++ b/src/__stories__/createReducer.story.tsx
@@ -0,0 +1,55 @@
+import { storiesOf } from '@storybook/react';
+import * as React from 'react';
+import logger from 'redux-logger';
+import thunk from 'redux-thunk';
+
+import { createReducer } from '..';
+
+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 dispatch2 => {
+ dispatch2({ type: 'increment' });
+
+ setTimeout(() => {
+ dispatch2({ 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 00000000..fb0d0b8c
--- /dev/null
+++ b/src/createReducer.ts
@@ -0,0 +1,36 @@
+import { useMemo, useRef, useState } from 'react';
+
+function composeMiddleware(chain) {
+ return (context, dispatch) => {
+ return chain.reduceRight((res, middleware) => {
+ return middleware(context)(res);
+ }, dispatch);
+ };
+}
+
+const createReducer = (...middlewares) => (reducer, initialState, initializer = value => value) => {
+ const ref = useRef(initializer(initialState));
+ const [, setState] = useState(ref.current);
+ let middlewareDispatch = (_ = {}) => {
+ throw new Error(
+ 'Dispatching while constructing your middleware is not allowed. ' +
+ 'Other middleware would not be applied to this dispatch.'
+ );
+ };
+ const dispatch = action => {
+ ref.current = reducer(ref.current, action);
+ setState(ref.current);
+ return action;
+ };
+ const composedMiddleware = useMemo(() => {
+ return composeMiddleware(middlewares);
+ }, middlewares);
+ const middlewareAPI = {
+ getState: () => ref.current,
+ dispatch: (...args) => middlewareDispatch(...args),
+ };
+ middlewareDispatch = composedMiddleware(middlewareAPI, dispatch);
+ return [ref.current, middlewareDispatch];
+};
+
+export default createReducer;
diff --git a/src/index.ts b/src/index.ts
index 49af3511..8a475b93 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';
@@ -73,6 +74,7 @@ import useWindowSize from './useWindowSize';
export {
createMemo,
+ createReducer,
useAsync,
useAsyncFn,
useAsyncRetry,
diff --git a/yarn.lock b/yarn.lock
index 7b7e9419..2be2fffe 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5267,6 +5267,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"
@@ -11894,6 +11899,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"