Skip to content

Commit

Permalink
feat: add useFirstMountState & useRendersCount hooks (#769)
Browse files Browse the repository at this point in the history
feat(useFirstMountState): hook to track if render is first;
feat(useRendersCount): hook to track renders count;
refactor(useUpdateEffect): now uses useFirstMountState hook;
refactor(usePreviousDistinct): now uses with useFirstMountState hook;
docs: update readme;
  • Loading branch information
xobotyi committed Nov 23, 2019
1 parent be27aaa commit 30abe2b
Show file tree
Hide file tree
Showing 12 changed files with 175 additions and 11 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@
- [`useStateValidator`](./docs/useStateValidator.md) — tracks state of an object. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-usestatevalidator--demo)
- [`useMultiStateValidator`](./docs/useMultiStateValidator.md) — alike the `useStateValidator`, but tracks multiple states at a time. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-usemultistatevalidator--demo)
- [`useMediatedState`](./docs/useMediatedState.md) — like the regular `useState` but with mediation by custom function. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-usemediatedstate--demo)
- [`useFirstMountState`](./docs/useFirstMountState.md) — check if current render is first. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-usefirstmountstate--demo)
- [`useRendersCount`](./docs/useRendersCount.md) — count component renders. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-userenderscount--demo)
<br/>
<br/>
- [**Miscellaneous**]()
Expand Down
29 changes: 29 additions & 0 deletions docs/useFirstMountState.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# `useFirstMountState`

Returns `true` if component is just mounted (on first render) and `false` otherwise.

## Usage

```typescript jsx
import * as React from 'react';
import { useFirstMountState } from 'react-use';

const Demo = () => {
const isFirstMount = useFirstMountState();
const update = useUpdate();

return (
<div>
<span>This component is just mounted: {isFirstMount ? 'YES' : 'NO'}</span>
<br />
<button onClick={update}>re-render</button>
</div>
);
};
```

## Reference

```typescript
const isFirstMount: boolean = useFirstMountState();
```
29 changes: 29 additions & 0 deletions docs/useRendersCount.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# `useRendersCount`

Tracks compontent's renders count including the first render.

## Usage

```typescript jsx
import * as React from 'react';
import { useRendersCount } from "react-use";

const Demo = () => {
const update = useUpdate();
const rendersCount = useRendersCount();

return (
<div>
<span>Renders count: {rendersCount}</span>
<br />
<button onClick={update}>re-render</button>
</div>
);
};
```

## Reference

```typescript
const rendersCount: number = useRendersCount();
```
22 changes: 22 additions & 0 deletions src/__stories__/useFirstMountState.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { storiesOf } from '@storybook/react';
import * as React from 'react';
import { useFirstMountState } from '../useFirstMountState';
import useUpdate from '../useUpdate';
import ShowDocs from './util/ShowDocs';

const Demo = () => {
const isFirstMount = useFirstMountState();
const update = useUpdate();

return (
<div>
<span>This component is just mounted: {isFirstMount ? 'YES' : 'NO'}</span>
<br />
<button onClick={update}>re-render</button>
</div>
);
};

storiesOf('State|useFirstMountState', module)
.add('Docs', () => <ShowDocs md={require('../../docs/useFirstMountState.md')} />)
.add('Demo', () => <Demo />);
22 changes: 22 additions & 0 deletions src/__stories__/useRendersCount.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { storiesOf } from '@storybook/react';
import * as React from 'react';
import { useRendersCount } from '../useRendersCount';
import useUpdate from '../useUpdate';
import ShowDocs from './util/ShowDocs';

const Demo = () => {
const update = useUpdate();
const rendersCount = useRendersCount();

return (
<div>
<span>Renders count: {rendersCount}</span>
<br />
<button onClick={update}>re-render</button>
</div>
);
};

storiesOf('State|useRendersCount', module)
.add('Docs', () => <ShowDocs md={require('../../docs/useRendersCount.md')} />)
.add('Demo', () => <Demo />);
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,6 @@ export { useMultiStateValidator } from './useMultiStateValidator';
export { default as useWindowScroll } from './useWindowScroll';
export { default as useWindowSize } from './useWindowSize';
export { default as useMeasure } from './useMeasure';
export { useRendersCount } from './useRendersCount';
export { useFirstMountState } from './useFirstMountState';
export { default as useSet } from './useSet';
13 changes: 13 additions & 0 deletions src/useFirstMountState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useRef } from 'react';

export function useFirstMountState(): boolean {
const isFirst = useRef(true);

if (isFirst.current) {
isFirst.current = false;

return true;
}

return isFirst.current;
}
7 changes: 3 additions & 4 deletions src/usePreviousDistinct.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useRef } from 'react';
import { useFirstMountState } from './useFirstMountState';

export type Predicate<T> = (prev: T | undefined, next: T) => boolean;

Expand All @@ -7,11 +8,9 @@ const strictEquals = <T>(prev: T | undefined, next: T) => prev === next;
export default function usePreviousDistinct<T>(value: T, compare: Predicate<T> = strictEquals): T | undefined {
const prevRef = useRef<T>();
const curRef = useRef<T>(value);
const firstRender = useRef(true);
const isFirstMount = useFirstMountState();

if (firstRender.current) {
firstRender.current = false;
} else if (!compare(curRef.current, value)) {
if (!isFirstMount && !compare(curRef.current, value)) {
prevRef.current = curRef.current;
curRef.current = value;
}
Expand Down
5 changes: 5 additions & 0 deletions src/useRendersCount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { useRef } from 'react';

export function useRendersCount(): number {
return ++useRef(0).current;
}
11 changes: 4 additions & 7 deletions src/useUpdateEffect.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { useEffect, useRef } from 'react';
import { useEffect } from 'react';
import { useFirstMountState } from './useFirstMountState';

const useUpdateEffect: typeof useEffect = (effect, deps) => {
const isInitialMount = useRef(true);
const isFirstMount = useFirstMountState();

useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
} else {
return effect();
}
!isFirstMount && effect();
}, deps);
};

Expand Down
22 changes: 22 additions & 0 deletions tests/useFirstMountState.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { renderHook } from '@testing-library/react-hooks';
import { useFirstMountState } from '../src';

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

it('should return boolean', () => {
expect(renderHook(() => useFirstMountState()).result.current).toEqual(expect.any(Boolean));
});

it('should return true on first render and false on all others', () => {
const hook = renderHook(() => useFirstMountState());

expect(hook.result.current).toBe(true);
hook.rerender();
expect(hook.result.current).toBe(false);
hook.rerender();
expect(hook.result.current).toBe(false);
});
});
22 changes: 22 additions & 0 deletions tests/useRendersCount.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { renderHook } from '@testing-library/react-hooks';
import { useRendersCount } from '../src';

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

it('should return number', () => {
expect(renderHook(() => useRendersCount()).result.current).toEqual(expect.any(Number));
});

it('should return actual number of renders', () => {
const hook = renderHook(() => useRendersCount());

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

0 comments on commit 30abe2b

Please sign in to comment.