Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(useDeepCompareMemo): Implement useDeepCompareMemo (#979)
* 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
Showing
8 changed files
with
134 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |