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: add usePreviousDistinct #551

Merged
merged 4 commits into from
Aug 25, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions docs/usePreviousDistinct.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# `usePreviousDistinct`

Just like `usePrevious` but it will only update once the value actually changes. This is important when other
hooks are involved and you aren't just interested in the previous props version, but want to know the previous
distinct value

## Usage

```jsx
import {usePreviousDistinct} from 'react-use';

const Demo = () => {
const [count, setCount] = React.useState(0);
const [unrelatedCount, setUnrelatedCount] = React.useState(0);
const prevCount = usePreviousDistinct(count);

return (
<p>
Now: {count}, before: {prevCount}
Unrelated: {unrelatedCount}
</p>
);
};
```

You can also provide a way of identifying the value as unique. By default, a strict equals is used.

```jsx
import {usePreviousDistinct} from 'react-use';

const Demo = () => {
const [str, setStr] = React.useState("something_lowercase");
const prevStr = usePreviousDistinct(str, (prev, next) => (prev && prev.toUpperCase()) === next.toUpperCase());

return (
<p>
Now: {count}, before: {prevCount}
Unrelated: {unrelatedCount}
</p>
);
};
```

## Reference

```ts
const prevState = usePreviousDistinct = <T>(state: T, compare?: (prev: T | undefined, next: T) => boolean): T;
```
65 changes: 65 additions & 0 deletions src/__tests__/usePreviousDistinct.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { renderHook } from '@testing-library/react-hooks';
import usePreviousDistinct from '../usePreviousDistinct';

describe('usePreviousDistinct with default compare', () => {
const hook = renderHook(props => usePreviousDistinct(props), { initialProps: 0 });

it('should return undefined on initial render', () => {
expect(hook.result.current).toBe(undefined);
});

it('should return previous state only after a different value is rendered', () => {
expect(hook.result.current).toBeUndefined();
hook.rerender(1);
expect(hook.result.current).toBe(0);
hook.rerender(2);
hook.rerender(2);
expect(hook.result.current).toBe(1);

hook.rerender(3);
expect(hook.result.current).toBe(2);
});
});

describe('usePreviousDistinct with complex comparison', () => {
const exampleObjects = [
{
id: 'something-unique',
name: 'Nancy',
},
{
id: 'something-unique2',
name: 'Fred',
},
{
id: 'something-unique3',
name: 'Bill',
},
{
id: 'something-unique4',
name: 'Alice',
},
];
const hook = renderHook(
props => usePreviousDistinct(props, (prev, next) => (prev && prev.id) === (next && next.id)),
{
initialProps: exampleObjects[0],
}
);

it('should return undefined on initial render', () => {
expect(hook.result.current).toBe(undefined);
});

it('should return previous state only after a different value is rendered', () => {
expect(hook.result.current).toBeUndefined();
hook.rerender(exampleObjects[1]);
expect(hook.result.current).toMatchObject(exampleObjects[0]);
hook.rerender(exampleObjects[2]);
hook.rerender(exampleObjects[2]);
expect(hook.result.current).toMatchObject(exampleObjects[1]);

hook.rerender(exampleObjects[3]);
expect(hook.result.current).toMatchObject(exampleObjects[2]);
});
});
19 changes: 19 additions & 0 deletions src/usePreviousDistinct.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useRef } from 'react';

function strictEquals<T>(prev: T | undefined, next: T) {
return prev === next;
}

export default function usePreviousDistinct<T>(
value: T,
compare: (prev: T | undefined, next: T) => boolean = strictEquals
) {
const prevRef = useRef<T>();
const curRef = useRef<T>();
if (!compare(curRef.current, value)) {
prevRef.current = curRef.current;
curRef.current = value;
}

return prevRef.current;
}