Skip to content

Commit

Permalink
feat: add useLatest hook
Browse files Browse the repository at this point in the history
  • Loading branch information
ClementParis016 committed May 10, 2020
1 parent b3d14e2 commit d6fe267
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
- [`useDefault`](./docs/useDefault.md) — returns the default value when state is `null` or `undefined`.
- [`useGetSet`](./docs/useGetSet.md) — returns state getter `get()` instead of raw state.
- [`useGetSetState`](./docs/useGetSetState.md) — as if [`useGetSet`](./docs/useGetSet.md) and [`useSetState`](./docs/useSetState.md) had a baby.
- [`useLatest`](./docs/useLatest.md) — returns the latest state or props
- [`usePrevious`](./docs/usePrevious.md) — returns the previous state or props. [![][img-demo]](https://codesandbox.io/s/fervent-galileo-krgx6)
- [`usePreviousDistinct`](./docs/usePreviousDistinct.md) — like `usePrevious` but with a predicate to determine if `previous` should update.
- [`useObservable`](./docs/useObservable.md) — tracks latest value of an `Observable`.
Expand Down
36 changes: 36 additions & 0 deletions docs/useLatest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# `useLatest`

React state hook that returns the latest state as described in the [React hooks FAQ](https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function).

This is mostly useful to get access to the latest value of some props or state inside an asynchronous callback, instead of that value at the time the callback was created from.

## Usage

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

const Demo = () => {
const [count, setCount] = React.useState(0);
const latestCount = useLatest(count);

function handleAlertClick() {
setTimeout(() => {
alert(`Latest count value: ${latestCount.current}`);
}, 3000);
}

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
<button onClick={handleAlertClick}>Show alert</button>
</div>
);
};
```

## Reference

```ts
const latestState = useLatest = <T>(state: T): MutableRefObject<T>;
```
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export { default as createBreakpoint } from './createBreakpoint';
// export { default as useKeyboardJs } from './useKeyboardJs';
export { default as useKeyPress } from './useKeyPress';
export { default as useKeyPressEvent } from './useKeyPressEvent';
export { default as useLatest } from './useLatest';
export { default as useLifecycles } from './useLifecycles';
export { default as useList } from './useList';
export { default as useLocalStorage } from './useLocalStorage';
Expand Down Expand Up @@ -110,4 +111,4 @@ export { useRendersCount } from './useRendersCount';
export { useFirstMountState } from './useFirstMountState';
export { default as useSet } from './useSet';
export { createGlobalState } from './createGlobalState';
export { useHash } from './useHash'
export { useHash } from './useHash';
13 changes: 13 additions & 0 deletions src/useLatest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useRef, useEffect, MutableRefObject } from 'react';

const useLatest = <T>(value: T): MutableRefObject<T> => {
const latest = useRef<T>(value);

useEffect(() => {
latest.current = value;
}, [value]);

return latest;
};

export default useLatest;
27 changes: 27 additions & 0 deletions stories/useLatest.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { storiesOf } from '@storybook/react';
import * as React from 'react';
import { useLatest } from '../src';
import ShowDocs from './util/ShowDocs';

const Demo = () => {
const [count, setCount] = React.useState(0);
const latestCount = useLatest(count);

function handleAlertClick() {
setTimeout(() => {
alert(`Latest count value: ${latestCount.current}`);
}, 3000);
}

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
<button onClick={handleAlertClick}>Show alert</button>
</div>
);
};

storiesOf('State|useLatest', module)
.add('Docs', () => <ShowDocs md={require('../docs/useLatest.md')} />)
.add('Demo', () => <Demo />);
23 changes: 23 additions & 0 deletions tests/useLatest.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { renderHook } from '@testing-library/react-hooks';
import useLatest from '../src/useLatest';

const setUp = () => renderHook(({ state }) => useLatest(state), { initialProps: { state: 0 } });

it('should return a ref with the latest value on initial render', () => {
const { result } = setUp();

expect(result.current).toEqual({ current: 0 });
});

it('should always return a ref with the latest value after each update', () => {
const { result, rerender } = setUp();

rerender({ state: 2 });
expect(result.current).toEqual({ current: 2 });

rerender({ state: 4 });
expect(result.current).toEqual({ current: 4 });

rerender({ state: 6 });
expect(result.current).toEqual({ current: 6 });
});

0 comments on commit d6fe267

Please sign in to comment.