Skip to content

Commit 8596a21

Browse files
committed
add memoization together with proper unit test for it
1 parent da1a63f commit 8596a21

3 files changed

Lines changed: 31 additions & 8 deletions

File tree

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "roth.js",
3-
"version": "3.0.0",
3+
"version": "3.1.0",
44
"description": "roth.js - Tiny react-redux extension library for easier action/dispatch/reducer management",
55
"author": "sidecus",
66
"license": "MIT",
@@ -25,8 +25,8 @@
2525
"build": "microbundle --tsconfig tsconfig.build.json",
2626
"start": "microbundle --tsconfig tsconfig.build.json --no-compress",
2727
"test": "run-s test:unit test:lint",
28-
"test:lint": "eslint --ext ts,tsx .",
29-
"test:unit": "cross-env CI=1 tsc && react-scripts test --env=jsdom",
28+
"test:lint": "tsc && eslint --ext ts,tsx .",
29+
"test:unit": "cross-env CI=1 react-scripts test --env=jsdom",
3030
"test:watch": "tsc && react-scripts test --env=jsdom"
3131
},
3232
"peerDependencies": {

src/hooks.test.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ jest.mock('react-redux', () => ({
1010

1111
const useDispatchMock = useDispatch as jest.Mock;
1212

13+
// Define this at global scope.
14+
// useBoundActions uses memoization and function scoped param
15+
// will defeat memoization.
1316
const actionCreators = {
1417
numberAction: createActionCreator<number>('numberaction'),
1518
stringAction: createActionCreator<string>('stringaction'),
@@ -44,4 +47,24 @@ describe('useBoundActions behaviors', () => {
4447
voidAction();
4548
expect(dispatchResultRecorder.voidaction).toBe('void');
4649
});
50+
51+
it('useBoundAction memoizes behavior', () => {
52+
useDispatchMock.mockClear();
53+
54+
const { result, rerender } = renderHook(() => useBoundActions(actionCreators));
55+
expect(result.error).toBeUndefined();
56+
expect(useDispatchMock).toHaveBeenCalledTimes(1);
57+
58+
const { numberAction, stringAction, voidAction } = result.current;
59+
60+
// rerender, it'll invoke useBoundActions again.
61+
rerender();
62+
63+
// verify the same results are returned since neither actionCreators nor dispatch have been changed
64+
expect(result.error).toBeUndefined();
65+
expect(useDispatchMock).toHaveBeenCalledTimes(2);
66+
expect(result.current.numberAction).toBe(numberAction);
67+
expect(result.current.stringAction).toBe(stringAction);
68+
expect(result.current.voidAction).toBe(voidAction);
69+
});
4770
});

src/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1+
import { useMemo } from 'react';
12
import { useDispatch } from 'react-redux';
23
import { Action as ReduxAction, bindActionCreators, ActionCreatorsMapObject } from 'redux';
34

4-
/**
5-
* ============Hooks based bound action creator related type definitions=========================
6-
*/
7-
85
/**
96
* Custom hooks to create an object containing named action creators with dispatch bound automatically using redux hooks.
107
* Object is memoized so won't get created each time you use this custom hooks.
118
* Version 3.0.0 - switch to bindActionCreators instead of our own implementation and remove memoization.
9+
* Version 3.0.1 - add back memoization. Most apps will use this in useEffect and memoization can help with avoid unwanted rerendering if this is in the dependency tree.
1210
* @template M type of the object contains named action creators (plain action creator or thunk action creator)
1311
* @param map the object contains named action creators.
1412
*/
1513
export const useBoundActions = <M extends ActionCreatorsMapObject>(map: M): M => {
1614
const dispatch = useDispatch();
17-
return bindActionCreators(map, dispatch);
15+
return useMemo(() => {
16+
return bindActionCreators(map, dispatch);
17+
}, [dispatch, map]);
1818
};
1919

2020
/**

0 commit comments

Comments
 (0)