From bc3a655f5cbfe3b4edb94c6084f62e95806ea6de Mon Sep 17 00:00:00 2001 From: xobotyi Date: Sat, 17 Apr 2021 00:22:04 +0300 Subject: [PATCH] feat: useUpdateEffect hook --- src/useUpdateEffect.ts | 14 ++++++++++ tests/dom/useUpdateEffect.test.ts | 43 ++++++++++++++++++++++++++++++ tests/ssr/useMountEffect.test.ts | 3 +-- tests/ssr/useUnmountEffect.test.ts | 3 +-- tests/ssr/useUpdateEffect.test.ts | 12 +++++++++ 5 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 src/useUpdateEffect.ts create mode 100644 tests/dom/useUpdateEffect.test.ts create mode 100644 tests/ssr/useUpdateEffect.test.ts diff --git a/src/useUpdateEffect.ts b/src/useUpdateEffect.ts new file mode 100644 index 000000000..c26c7698f --- /dev/null +++ b/src/useUpdateEffect.ts @@ -0,0 +1,14 @@ +import { DependencyList, EffectCallback, useEffect } from 'react'; +import { useFirstMountState } from './useFirstMountState'; + +export function useUpdateEffect(effect: EffectCallback, deps?: DependencyList): void { + const isFirstMount = useFirstMountState(); + + // eslint-disable-next-line consistent-return + useEffect(() => { + if (!isFirstMount) { + return effect(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, deps); +} diff --git a/tests/dom/useUpdateEffect.test.ts b/tests/dom/useUpdateEffect.test.ts new file mode 100644 index 000000000..9f8561de5 --- /dev/null +++ b/tests/dom/useUpdateEffect.test.ts @@ -0,0 +1,43 @@ +import { renderHook } from '@testing-library/react-hooks/dom'; +import { useUpdateEffect } from '../../src/useUpdateEffect'; + +describe('useUpdateEffect', () => { + it('should call effector only on updates (after first render)', () => { + const spy = jest.fn(); + + const { rerender, unmount } = renderHook(() => useUpdateEffect(spy)); + + expect(spy).toHaveBeenCalledTimes(0); + + rerender(); + expect(spy).toHaveBeenCalledTimes(1); + + rerender(); + expect(spy).toHaveBeenCalledTimes(2); + + unmount(); + expect(spy).toHaveBeenCalledTimes(2); + }); + + it('should accept dependencies as useEffect', () => { + const spy = jest.fn(); + + const { rerender, unmount } = renderHook(({ deps }) => useUpdateEffect(spy, deps), { + initialProps: { deps: [1, 2, 3] }, + }); + + expect(spy).toHaveBeenCalledTimes(0); + + rerender(); + expect(spy).toHaveBeenCalledTimes(0); + + rerender({ deps: [1, 2, 4] }); + expect(spy).toHaveBeenCalledTimes(1); + + rerender({ deps: [1, 2, 4] }); + expect(spy).toHaveBeenCalledTimes(1); + + unmount(); + expect(spy).toHaveBeenCalledTimes(1); + }); +}); diff --git a/tests/ssr/useMountEffect.test.ts b/tests/ssr/useMountEffect.test.ts index c81acaf4a..ea01bf113 100644 --- a/tests/ssr/useMountEffect.test.ts +++ b/tests/ssr/useMountEffect.test.ts @@ -5,9 +5,8 @@ describe('useMountEffect', () => { it('should call effector only on first render', () => { const spy = jest.fn(); - const { result } = renderHook(() => useMountEffect(spy)); + renderHook(() => useMountEffect(spy)); - expect(result.current).toBe(undefined); expect(spy).toHaveBeenCalledTimes(0); }); }); diff --git a/tests/ssr/useUnmountEffect.test.ts b/tests/ssr/useUnmountEffect.test.ts index 08fe3dad5..b978f022a 100644 --- a/tests/ssr/useUnmountEffect.test.ts +++ b/tests/ssr/useUnmountEffect.test.ts @@ -5,9 +5,8 @@ describe('useUnmountEffect', () => { it('should call effector only when component unmounted', () => { const spy = jest.fn(); - const { result } = renderHook(() => useUnmountEffect(spy)); + renderHook(() => useUnmountEffect(spy)); - expect(result.current).toBe(undefined); expect(spy).toHaveBeenCalledTimes(0); }); }); diff --git a/tests/ssr/useUpdateEffect.test.ts b/tests/ssr/useUpdateEffect.test.ts new file mode 100644 index 000000000..2e45f48ed --- /dev/null +++ b/tests/ssr/useUpdateEffect.test.ts @@ -0,0 +1,12 @@ +import { renderHook } from '@testing-library/react-hooks/server'; +import { useUpdateEffect } from '../../src/useUpdateEffect'; + +describe('useUpdateEffect', () => { + it('should not call effector on mount', () => { + const spy = jest.fn(); + + renderHook(() => useUpdateEffect(spy)); + + expect(spy).toHaveBeenCalledTimes(0); + }); +});