Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(useDeepCompareMemo): Implement useDeepCompareMemo #979

Merged
merged 2 commits into from Oct 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);
}