From 8596a215829d2a2ffcd83e6cec7f02bf1ebc8e3c Mon Sep 17 00:00:00 2001 From: sidecus <4399408+sidecus@users.noreply.github.com> Date: Wed, 26 Aug 2020 11:17:11 -0700 Subject: [PATCH] add memoization together with proper unit test for it --- package.json | 6 +++--- src/hooks.test.tsx | 23 +++++++++++++++++++++++ src/index.ts | 10 +++++----- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 49bea51..01eff72 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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": { diff --git a/src/hooks.test.tsx b/src/hooks.test.tsx index 9c8d7a7..6303c6a 100644 --- a/src/hooks.test.tsx +++ b/src/hooks.test.tsx @@ -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('numberaction'), stringAction: createActionCreator('stringaction'), @@ -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); + }); }); diff --git a/src/index.ts b/src/index.ts index 6617092..5f00689 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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 = (map: M): M => { const dispatch = useDispatch(); - return bindActionCreators(map, dispatch); + return useMemo(() => { + return bindActionCreators(map, dispatch); + }, [dispatch, map]); }; /**