diff --git a/README.md b/README.md
index 4264706cea..e35d791c00 100644
--- a/README.md
+++ b/README.md
@@ -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)
- [**Miscellaneous**]()
diff --git a/docs/useFirstMountState.md b/docs/useFirstMountState.md
new file mode 100644
index 0000000000..8d2a737389
--- /dev/null
+++ b/docs/useFirstMountState.md
@@ -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 (
+
+ This component is just mounted: {isFirstMount ? 'YES' : 'NO'}
+
+ re-render
+
+ );
+};
+```
+
+## Reference
+
+```typescript
+const isFirstMount: boolean = useFirstMountState();
+```
diff --git a/docs/useRendersCount.md b/docs/useRendersCount.md
new file mode 100644
index 0000000000..05f6111125
--- /dev/null
+++ b/docs/useRendersCount.md
@@ -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 (
+
+ Renders count: {rendersCount}
+
+ re-render
+
+ );
+};
+```
+
+## Reference
+
+```typescript
+const rendersCount: number = useRendersCount();
+```
diff --git a/src/__stories__/useFirstMountState.story.tsx b/src/__stories__/useFirstMountState.story.tsx
new file mode 100644
index 0000000000..d56151d1e5
--- /dev/null
+++ b/src/__stories__/useFirstMountState.story.tsx
@@ -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 (
+
+ This component is just mounted: {isFirstMount ? 'YES' : 'NO'}
+
+ re-render
+
+ );
+};
+
+storiesOf('State|useFirstMountState', module)
+ .add('Docs', () => )
+ .add('Demo', () => );
diff --git a/src/__stories__/useRendersCount.story.tsx b/src/__stories__/useRendersCount.story.tsx
new file mode 100644
index 0000000000..2b07ac377a
--- /dev/null
+++ b/src/__stories__/useRendersCount.story.tsx
@@ -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 (
+
+ Renders count: {rendersCount}
+
+ re-render
+
+ );
+};
+
+storiesOf('State|useRendersCount', module)
+ .add('Docs', () => )
+ .add('Demo', () => );
diff --git a/src/index.ts b/src/index.ts
index 1b4dd8946e..08e4383a34 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -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';
diff --git a/src/useFirstMountState.ts b/src/useFirstMountState.ts
new file mode 100644
index 0000000000..cf210622a2
--- /dev/null
+++ b/src/useFirstMountState.ts
@@ -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;
+}
diff --git a/src/usePreviousDistinct.ts b/src/usePreviousDistinct.ts
index 9e44524ac1..896afc73fe 100644
--- a/src/usePreviousDistinct.ts
+++ b/src/usePreviousDistinct.ts
@@ -1,4 +1,5 @@
import { useRef } from 'react';
+import { useFirstMountState } from './useFirstMountState';
export type Predicate = (prev: T | undefined, next: T) => boolean;
@@ -7,11 +8,9 @@ const strictEquals = (prev: T | undefined, next: T) => prev === next;
export default function usePreviousDistinct(value: T, compare: Predicate = strictEquals): T | undefined {
const prevRef = useRef();
const curRef = useRef(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;
}
diff --git a/src/useRendersCount.ts b/src/useRendersCount.ts
new file mode 100644
index 0000000000..ccb8681b8b
--- /dev/null
+++ b/src/useRendersCount.ts
@@ -0,0 +1,5 @@
+import { useRef } from 'react';
+
+export function useRendersCount(): number {
+ return ++useRef(0).current;
+}
diff --git a/src/useUpdateEffect.ts b/src/useUpdateEffect.ts
index 63fbc80c66..c180d73700 100644
--- a/src/useUpdateEffect.ts
+++ b/src/useUpdateEffect.ts
@@ -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);
};
diff --git a/tests/useFirstMountState.test.ts b/tests/useFirstMountState.test.ts
new file mode 100644
index 0000000000..c6a677a973
--- /dev/null
+++ b/tests/useFirstMountState.test.ts
@@ -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);
+ });
+});
diff --git a/tests/useRendersCount.test.ts b/tests/useRendersCount.test.ts
new file mode 100644
index 0000000000..a18122a0c5
--- /dev/null
+++ b/tests/useRendersCount.test.ts
@@ -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);
+ });
+});