Skip to content

Commit

Permalink
add memoization together with proper unit test for it
Browse files Browse the repository at this point in the history
  • Loading branch information
sidecus committed Aug 26, 2020
1 parent da1a63f commit 8596a21
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 8 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "roth.js",
"version": "3.0.0",
"version": "3.1.0",
"description": "roth.js - Tiny react-redux extension library for easier action/dispatch/reducer management",
"author": "sidecus",
"license": "MIT",
Expand All @@ -25,8 +25,8 @@
"build": "microbundle --tsconfig tsconfig.build.json",
"start": "microbundle --tsconfig tsconfig.build.json --no-compress",
"test": "run-s test:unit test:lint",
"test:lint": "eslint --ext ts,tsx .",
"test:unit": "cross-env CI=1 tsc && react-scripts test --env=jsdom",
"test:lint": "tsc && eslint --ext ts,tsx .",
"test:unit": "cross-env CI=1 react-scripts test --env=jsdom",
"test:watch": "tsc && react-scripts test --env=jsdom"
},
"peerDependencies": {
Expand Down
23 changes: 23 additions & 0 deletions src/hooks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ jest.mock('react-redux', () => ({

const useDispatchMock = useDispatch as jest.Mock;

// Define this at global scope.
// useBoundActions uses memoization and function scoped param
// will defeat memoization.
const actionCreators = {
numberAction: createActionCreator<number>('numberaction'),
stringAction: createActionCreator<string>('stringaction'),
Expand Down Expand Up @@ -44,4 +47,24 @@ describe('useBoundActions behaviors', () => {
voidAction();
expect(dispatchResultRecorder.voidaction).toBe('void');
});

it('useBoundAction memoizes behavior', () => {
useDispatchMock.mockClear();

const { result, rerender } = renderHook(() => useBoundActions(actionCreators));
expect(result.error).toBeUndefined();
expect(useDispatchMock).toHaveBeenCalledTimes(1);

const { numberAction, stringAction, voidAction } = result.current;

// rerender, it'll invoke useBoundActions again.
rerender();

// verify the same results are returned since neither actionCreators nor dispatch have been changed
expect(result.error).toBeUndefined();
expect(useDispatchMock).toHaveBeenCalledTimes(2);
expect(result.current.numberAction).toBe(numberAction);
expect(result.current.stringAction).toBe(stringAction);
expect(result.current.voidAction).toBe(voidAction);
});
});
10 changes: 5 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { Action as ReduxAction, bindActionCreators, ActionCreatorsMapObject } from 'redux';

/**
* ============Hooks based bound action creator related type definitions=========================
*/

/**
* Custom hooks to create an object containing named action creators with dispatch bound automatically using redux hooks.
* Object is memoized so won't get created each time you use this custom hooks.
* Version 3.0.0 - switch to bindActionCreators instead of our own implementation and remove memoization.
* 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.
* @template M type of the object contains named action creators (plain action creator or thunk action creator)
* @param map the object contains named action creators.
*/
export const useBoundActions = <M extends ActionCreatorsMapObject>(map: M): M => {
const dispatch = useDispatch();
return bindActionCreators(map, dispatch);
return useMemo(() => {
return bindActionCreators(map, dispatch);
}, [dispatch, map]);
};

/**
Expand Down

0 comments on commit 8596a21

Please sign in to comment.