Skip to content

Commit

Permalink
feat(useDeepCompareMemo): Implement useDeepCompareMemo (#979)
Browse files Browse the repository at this point in the history
* feat(useDeepCompareMemo): Implement useDeepCompareMemo

Implement useDeepCompareMemo by combining useCustomCompareMemo and the isEqual function from
react-hookz/deep-equal.

Closes #871

Co-authored-by: xobotyi <xog3@yandex.ru>
  • Loading branch information
ArttuOll and xobotyi committed Oct 23, 2022
1 parent 2a42e6c commit 532cc41
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -158,6 +158,9 @@ Coming from `react-use`? Check out our
— Like `useRef`, but it returns immutable ref that contains actual value.
- [**`useCustomCompareMemo`**](https://react-hookz.github.io/web/?path=/docs/miscellaneous-useCustomCompareMemo--example)
— Like useMemo but uses provided comparator function to validate dependency changes.
- [**`useDeepCompareMemo`**](https://react-hookz.github.io/web/?path=/docs/miscellaneous-useDeepCompareMemo--example)
— Like `useMemo` but uses `@react-hookz/deep-equal` comparator function to validate deep
dependency changes.
- [**`useHookableRef`**](https://react-hookz.github.io/web/?path=/docs/miscellaneous-usehookableref--example)
— Like `useRef` but it is possible to define get and set handlers.

Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Expand Up @@ -114,3 +114,5 @@ export { resolveHookState } from './util/resolveHookState';

// Types
export * from './types';

export { useDeepCompareMemo } from './useDeepCompareMemo/useDeepCompareMemo';
1 change: 1 addition & 0 deletions src/useDeepCompareEffect/__docs__/story.mdx
Expand Up @@ -11,6 +11,7 @@ changes.

- SSR-friendly, meaning that comparator won't be called on the server.
- Ability to change underlying effect hook (default to `useEffect`).
- Uses yet fastest deep-comparator - [@react-hookz/deep-equal](https://github.com/react-hookz/deep-equal).

#### Example

Expand Down
27 changes: 27 additions & 0 deletions src/useDeepCompareMemo/__docs__/example.stories.tsx
@@ -0,0 +1,27 @@
import * as React from 'react';
import { useMemo } from 'react';
import { useRerender } from '../../useRerender/useRerender';
import { useDeepCompareMemo } from '../useDeepCompareMemo';

export const Example: React.FC = () => {
const newOnEveryRender = { value: 'Foo' };
// eslint-disable-next-line react-hooks/exhaustive-deps
const unstable = useMemo(() => Math.floor(Math.random() * 10), [newOnEveryRender]);

const stable = useDeepCompareMemo(() => Math.floor(Math.random() * 10), [newOnEveryRender]);

const rerender = useRerender();
return (
<>
<div style={{ display: 'flex', gap: '0.5rem' }}>
<p>When you click this button:</p>
<button onClick={rerender}>Rerender</button>
<p>, you notice, that the useDeepCompareMemo value does not change at all,</p>
</div>
<p>even though its dependencies change on every render.</p>
<br />
<p>useMemo: {unstable}</p>
<p>useDeepCompareMemo: {stable}</p>
</>
);
};
42 changes: 42 additions & 0 deletions src/useDeepCompareMemo/__docs__/story.mdx
@@ -0,0 +1,42 @@
import { Canvas, Meta, Story } from '@storybook/addon-docs/blocks'
import { Example } from './example.stories'
import { ImportPath } from '../../__docs__/ImportPath'

<Meta title="Miscellaneous/useDeepCompareMemo" component={Example} />

# useDeepCompareMemo

Like `useMemo` but uses `@react-hookz/deep-equal` comparator function to validate deep dependency changes.

- SSR-friendly, meaning that the comparator won't be called on the server.
- Uses yet fastest deep-comparator - [@react-hookz/deep-equal](https://github.com/react-hookz/deep-equal).

#### Example

<Canvas>
<Story story={Example} />
</Canvas>

## Reference

```ts
export function useDeepCompareMemo<T, Deps extends DependencyList>(
factory: () => T,
deps: Deps
): T
```

#### Importing

<ImportPath />

#### Arguments

- **factory** `() => T` - Function calculating the memoized value. Passed to the underlying `useMemo`.
- **deps** `DependencyList` - List of all reactive values referenced by `factory`. Passed to the `deps` parameter of the underlying `useMemo`.

#### Return

Initially returns the result of calling `factory`. This value is memoized and returned on every
render, until the dependencies change, determined by deep comparison, at which point `factory` will be called again and the resulting
value will be memoized.
31 changes: 31 additions & 0 deletions src/useDeepCompareMemo/__tests__/dom.ts
@@ -0,0 +1,31 @@
import { renderHook } from '@testing-library/react-hooks/dom';
import { useDeepCompareMemo } from '../useDeepCompareMemo';

describe('useDeepCompareMemo', () => {
it('should be defined', () => {
expect(useDeepCompareMemo).toBeDefined();
});

it('should render', () => {
const { result } = renderHook(() => useDeepCompareMemo(() => {}, []));
expect(result.error).toBeUndefined();
});

it('should run only if dependencies change, defined by deep comparison', () => {
const spy = jest.fn();
const { rerender } = renderHook(({ deps }) => useDeepCompareMemo(spy, deps), {
initialProps: { deps: [{ foo: 'bar' }] },
});

expect(spy).toHaveBeenCalledTimes(1);

rerender({ deps: [{ foo: 'bar' }] });
expect(spy).toHaveBeenCalledTimes(1);

rerender({ deps: [{ foo: 'baz' }] });
expect(spy).toHaveBeenCalledTimes(2);

rerender({ deps: [{ foo: 'baz' }] });
expect(spy).toHaveBeenCalledTimes(2);
});
});
13 changes: 13 additions & 0 deletions src/useDeepCompareMemo/__tests__/ssr.ts
@@ -0,0 +1,13 @@
import { renderHook } from '@testing-library/react-hooks/server';
import { useDeepCompareMemo } from '../..';

describe('useDeepCompareMemo', () => {
it('should be defined', () => {
expect(useDeepCompareMemo).toBeDefined();
});

it('should render', () => {
const { result } = renderHook(() => useDeepCompareMemo(() => {}, []));
expect(result.error).toBeUndefined();
});
});
15 changes: 15 additions & 0 deletions src/useDeepCompareMemo/useDeepCompareMemo.ts
@@ -0,0 +1,15 @@
import { DependencyList } from 'react';
import { isEqual } from '@react-hookz/deep-equal';
import { useCustomCompareMemo } from '../useCustomCompareMemo/useCustomCompareMemo';

/**
* Like useMemo but validates dependency changes using deep equality check instead of reference check.
*
* @param factory Function calculating the value to be memoized.
* @param deps The list of all reactive values referenced inside `factory`.
* @returns Initially returns the result of calling `factory`. On subsequent renders, it will return
* the same value, if dependencies haven't changed, or the result of calling `factory` again, if they have changed.
*/
export function useDeepCompareMemo<T, Deps extends DependencyList>(factory: () => T, deps: Deps) {
return useCustomCompareMemo(factory, deps, isEqual);
}

0 comments on commit 532cc41

Please sign in to comment.