chore: 🤖 catch up with master

This commit is contained in:
streamich 2020-01-17 09:24:06 +01:00
commit 973a11fdb5
21 changed files with 1264 additions and 720 deletions

View File

@ -1,3 +1,50 @@
# [13.19.0](https://github.com/streamich/react-use/compare/v13.18.0...v13.19.0) (2020-01-16)
### Features
* add useError hook ([65f3644](https://github.com/streamich/react-use/commit/65f364420524bacebe8f8149b8197fb62bff1a08))
# [13.18.0](https://github.com/streamich/react-use/compare/v13.17.0...v13.18.0) (2020-01-16)
### Bug Fixes
* check for null ([d619c39](https://github.com/streamich/react-use/commit/d619c39a21e9f0b4b4bfc6a209311bf0bd495f9b))
### Features
* add serializer/deserializer option to useLocalStorage ([5316510](https://github.com/streamich/react-use/commit/5316510babf7606a2f4b78de2b0eb85c930890cf))
# [13.17.0](https://github.com/streamich/react-use/compare/v13.16.1...v13.17.0) (2020-01-15)
### Features
* add support for body lock on iOS ([d778408](https://github.com/streamich/react-use/commit/d7784084fe84aca72efe85260101b00ef1df7580))
## [13.16.1](https://github.com/streamich/react-use/compare/v13.16.0...v13.16.1) (2020-01-14)
### Bug Fixes
* update the types dep for js-cookie ([5c55d59](https://github.com/streamich/react-use/commit/5c55d59a7d1d799cba7af87e15ab4a4b27a8fc67))
# [13.16.0](https://github.com/streamich/react-use/compare/v13.15.0...v13.16.0) (2020-01-14)
### Features
* add option to useTitle to restore title on un-mount ([b8b3e47](https://github.com/streamich/react-use/commit/b8b3e479cea6071d4310bac29f138bd8917eee0b))
# [13.15.0](https://github.com/streamich/react-use/compare/v13.14.3...v13.15.0) (2020-01-13)
### Features
* add useCookie hook ([4e5c90f](https://github.com/streamich/react-use/commit/4e5c90f021f56ae2008dc25daad69c43063f608f))
## [13.14.3](https://github.com/streamich/react-use/compare/v13.14.2...v13.14.3) (2020-01-08)

View File

@ -95,8 +95,10 @@
- [**Side-effects**](./docs/Side-effects.md)
- [`useAsync`](./docs/useAsync.md), [`useAsyncFn`](./docs/useAsyncFn.md), and [`useAsyncRetry`](./docs/useAsyncRetry.md) — resolves an `async` function.
- [`useBeforeUnload`](./docs/useBeforeUnload.md) — shows browser alert when user try to reload or close the page.
- [`useCookie`](./docs/useCookie.md) — provides way to read, update and delete a cookie. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/side-effects-usecookie--demo)
- [`useCopyToClipboard`](./docs/useCopyToClipboard.md) — copies text to clipboard.
- [`useDebounce`](./docs/useDebounce.md) — debounces a function. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/side-effects-usedebounce--demo)
- [`useError`](./docs/useError.md) — error dispatcher. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/side-effects-useerror--demo)
- [`useFavicon`](./docs/useFavicon.md) — sets favicon of the page.
- [`useLocalStorage`](./docs/useLocalStorage.md) — manages a value in `localStorage`.
- [`useLockBodyScroll`](./docs/useLockBodyScroll.md) — lock scrolling of the body element.
@ -150,7 +152,6 @@
- [**Miscellaneous**]()
- [`useEnsuredForwardedRef`](./docs/useEnsuredForwardedRef.md) and [`ensuredForwardRef`](./docs/useEnsuredForwardedRef.md) — use a React.forwardedRef safely. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-useensuredforwardedref--demo)
<br />
<br />
<br />

39
docs/useCookie.md Normal file
View File

@ -0,0 +1,39 @@
# `useCookie`
React hook that returns the current value of a `cookie`, a callback to update the `cookie`
and a callback to delete the `cookie.`
## Usage
```jsx
import { useCookie } from "react-use";
const Demo = () => {
const [value, updateCookie, deleteCookie] = useCookie("my-cookie");
const [counter, setCounter] = useState(1);
useEffect(() => {
deleteCookie();
}, []);
const updateCookieHandler = () => {
updateCookie(`my-awesome-cookie-${counter}`);
setCounter(c => c + 1);
};
return (
<div>
<p>Value: {value}</p>
<button onClick={updateCookieHandler}>Update Cookie</button>
<br />
<button onClick={deleteCookie}>Delete Cookie</button>
</div>
);
};
```
## Reference
```ts
const [value, updateCookie, deleteCookie] = useCookie(cookieName: string);
```

34
docs/useError.md Normal file
View File

@ -0,0 +1,34 @@
# `useError`
React side-effect hook that returns an error dispatcher.
## Usage
```jsx
import { useError } from 'react-use';
const Demo = () => {
const dispatchError = useError();
const clickHandler = () => {
dispatchError(new Error('Some error!'));
};
return <button onClick={clickHandler}>Click me to throw</button>;
};
// In parent app
const App = () => (
<ErrorBoundary>
<Demo />
</ErrorBoundary>
);
```
## Reference
```js
const dispatchError = useError();
```
- `dispatchError` &mdash; Callback of type `(err: Error) => void`

View File

@ -2,11 +2,10 @@
React side-effect hook that manages a single `localStorage` key.
## Usage
```jsx
import {useLocalStorage} from 'react-use';
import { useLocalStorage } from 'react-use';
const Demo = () => {
const [value, setValue, remove] = useLocalStorage('my-key', 'foo');
@ -22,15 +21,21 @@ const Demo = () => {
};
```
## Reference
```js
useLocalStorage(key);
useLocalStorage(key, initialValue);
useLocalStorage(key, initialValue, raw);
useLocalStorage(key, initialValue, { raw: true });
useLocalStorage(key, initialValue, {
raw: false,
serializer: (value: T) => string,
deserializer: (value: string) => T,
});
```
- `key` &mdash; `localStorage` key to manage.
- `initialValue` &mdash; initial value to set, if value in `localStorage` is empty.
- `raw` &mdash; boolean, if set to `true`, hook will not attempt to JSON serialize stored values.
- `serializer` &mdash; custom serializer (defaults to `JSON.stringify`)
- `deserializer` &mdash; custom deserializer (defaults to `JSON.parse`)

View File

@ -46,9 +46,11 @@
},
"homepage": "https://github.com/streamich/react-use#readme",
"dependencies": {
"@types/js-cookie": "2.2.4",
"@xobotyi/scrollbar-width": "1.5.0",
"copy-to-clipboard": "^3.2.0",
"fast-shallow-equal": "^1.0.0",
"js-cookie": "^2.2.1",
"nano-css": "^5.2.1",
"react-fast-compare": "^2.0.4",
"react-universal-interface": "^0.6.0",
@ -64,22 +66,22 @@
"react-dom": "^16.8.0"
},
"devDependencies": {
"@babel/core": "7.8.0",
"@babel/plugin-syntax-dynamic-import": "7.8.0",
"@babel/preset-env": "7.8.0",
"@babel/preset-react": "7.8.0",
"@babel/preset-typescript": "7.8.0",
"@babel/core": "7.8.3",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.8.3",
"@babel/preset-react": "7.8.3",
"@babel/preset-typescript": "7.8.3",
"@semantic-release/changelog": "3.0.6",
"@semantic-release/git": "7.0.18",
"@semantic-release/npm": "5.3.5",
"@shopify/jest-dom-mocks": "2.8.8",
"@storybook/addon-actions": "5.3.1",
"@storybook/addon-knobs": "5.3.1",
"@storybook/addon-notes": "5.3.1",
"@storybook/addon-options": "5.3.1",
"@storybook/react": "5.3.1",
"@storybook/addon-actions": "5.3.5",
"@storybook/addon-knobs": "5.3.5",
"@storybook/addon-notes": "5.3.5",
"@storybook/addon-options": "5.3.5",
"@storybook/react": "5.3.5",
"@testing-library/react-hooks": "3.2.1",
"@types/jest": "24.0.25",
"@types/jest": "24.9.0",
"@types/react": "16.9.11",
"babel-core": "6.26.3",
"babel-loader": "8.0.6",
@ -88,6 +90,7 @@
"gh-pages": "2.2.0",
"husky": "3.1.0",
"jest": "24.9.0",
"jest-localstorage-mock": "2.4.0",
"keyboardjs": "2.5.1",
"lint-staged": "9.5.0",
"markdown-loader": "5.1.0",
@ -106,13 +109,13 @@
"semantic-release": "15.14.0",
"ts-jest": "24.3.0",
"ts-loader": "6.2.1",
"ts-node": "8.6.1",
"ts-node": "8.6.2",
"tslint": "6.0.0-beta1",
"tslint-config-prettier": "1.18.0",
"tslint-eslint-rules": "5.4.0",
"tslint-plugin-prettier": "2.1.0",
"tslint-react": "4.1.0",
"typescript": "3.7.4"
"typescript": "3.7.5"
},
"config": {
"commitizen": {
@ -160,7 +163,8 @@
"<rootDir>/tests/**/*.test.(ts|tsx)"
],
"setupFiles": [
"<rootDir>/tests/_setup.js"
"<rootDir>/tests/_setup.js",
"./tests/setupTests.ts"
]
}
}

View File

@ -8,6 +8,7 @@ export { default as useBattery } from './useBattery';
export { default as useBeforeUnload } from './useBeforeUnload';
export { default as useBoolean } from './useBoolean';
export { default as useClickAway } from './useClickAway';
export { default as useCookie } from './useCookie';
export { default as useCopyToClipboard } from './useCopyToClipboard';
export { default as useCounter } from './useCounter';
export { default as useCss } from './useCss';
@ -20,6 +21,7 @@ export { default as useDropArea } from './useDropArea';
export { default as useEffectOnce } from './useEffectOnce';
export { default as useEnsuredForwardedRef, ensuredForwardRef } from './useEnsuredForwardedRef';
export { default as useEvent } from './useEvent';
export { default as useError } from './useError';
export { default as useFavicon } from './useFavicon';
export { default as useFullscreen } from './useFullscreen';
export { default as useGeolocation } from './useGeolocation';

25
src/useCookie.ts Normal file
View File

@ -0,0 +1,25 @@
import { useState, useCallback } from 'react';
import Cookies from 'js-cookie';
const useCookie = (
cookieName: string
): [string | null, (newValue: string, options?: Cookies.CookieAttributes) => void, () => void] => {
const [value, setValue] = useState<string | null>(() => Cookies.get(cookieName) || null);
const updateCookie = useCallback(
(newValue: string, options?: Cookies.CookieAttributes) => {
Cookies.set(cookieName, newValue, options);
setValue(newValue);
},
[cookieName]
);
const deleteCookie = useCallback(() => {
Cookies.remove(cookieName);
setValue(null);
}, [cookieName]);
return [value, updateCookie, deleteCookie];
};
export default useCookie;

19
src/useError.ts Normal file
View File

@ -0,0 +1,19 @@
import { useState, useEffect, useCallback } from 'react';
const useError = (): ((err: Error) => void) => {
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
if (error) {
throw error;
}
}, [error]);
const dispatchError = useCallback((err: Error) => {
setError(err);
}, []);
return dispatchError;
};
export default useError;

View File

@ -1,8 +1,15 @@
import { useEffect, useState, useCallback } from 'react';
import { isClient } from './util';
type Dispatch<A> = (value: A) => void;
type SetStateAction<S> = S | ((prevState: S) => S);
type parserOptions<T> =
| {
raw: true;
}
| {
raw: false;
serializer: (value: T) => string;
deserializer: (value: string) => T;
};
const noop = () => {};
const isUndefined = (value?: any): boolean => typeof value === 'undefined';
@ -10,23 +17,25 @@ const isUndefined = (value?: any): boolean => typeof value === 'undefined';
const useLocalStorage = <T>(
key: string,
initialValue?: T,
raw?: boolean
): [T | undefined, Dispatch<SetStateAction<T | undefined>>, () => void] => {
options?: parserOptions<T>
): [T | undefined, React.Dispatch<React.SetStateAction<T | undefined>>, () => void] => {
if (!isClient) {
return [initialValue as T, noop, noop];
}
// Use provided serializer/deserializer or the default ones
const serializer = options ? (options.raw ? String : options.serializer) : JSON.stringify;
const deserializer = options ? (options.raw ? String : options.deserializer) : JSON.parse;
const [state, setState] = useState<T | undefined>(() => {
try {
const localStorageValue = localStorage.getItem(key);
if (isUndefined(initialValue)) {
return undefined;
}
if (typeof localStorageValue !== 'string') {
localStorage.setItem(key, raw ? String(initialValue) : JSON.stringify(initialValue));
if (localStorageValue !== null) {
return deserializer(localStorageValue);
} else {
initialValue && localStorage.setItem(key, serializer(initialValue));
return initialValue;
}
return raw ? localStorageValue : JSON.parse(localStorageValue || 'null');
} catch {
// If user is in private mode or has storage restriction
// localStorage can throw. JSON.parse and JSON.stringify
@ -48,8 +57,7 @@ const useLocalStorage = <T>(
useEffect(() => {
if (isUndefined(state)) return;
try {
const serializedState = raw ? String(state) : JSON.stringify(state);
localStorage.setItem(key, serializedState);
localStorage.setItem(key, serializer(state));
} catch {
// If user is in private mode or has storage restriction
// localStorage can throw. Also JSON.stringify can throw.

View File

@ -15,15 +15,33 @@ export function getClosestBody(el: Element | HTMLElement | HTMLIFrameElement | n
return getClosestBody((el as HTMLElement).offsetParent!);
}
function preventDefault(rawEvent: TouchEvent): boolean {
const e = rawEvent || window.event;
// Do not prevent if the event has more than one touch (usually meaning this is a multi touch gesture like pinch to zoom).
if (e.touches.length > 1) return true;
if (e.preventDefault) e.preventDefault();
return false;
}
export interface BodyInfoItem {
counter: number;
initialOverflow: CSSStyleDeclaration['overflow'];
}
const isIosDevice =
typeof window !== 'undefined' &&
window.navigator &&
window.navigator.platform &&
/iP(ad|hone|od)/.test(window.navigator.platform);
const bodies: Map<HTMLElement, BodyInfoItem> = new Map();
const doc: Document | undefined = typeof document === 'object' ? document : undefined;
let documentListenerAdded = false;
export default !doc
? function useLockBodyMock(_locked: boolean = true, _elementRef?: RefObject<HTMLElement>) {}
: function useLockBody(locked: boolean = true, elementRef?: RefObject<HTMLElement>) {
@ -40,7 +58,15 @@ export default !doc
if (locked) {
if (!bodyInfo) {
bodies.set(body, { counter: 1, initialOverflow: body.style.overflow });
body.style.overflow = 'hidden';
if (isIosDevice) {
if (!documentListenerAdded) {
document.addEventListener('touchmove', preventDefault, { passive: false });
documentListenerAdded = true;
}
} else {
body.style.overflow = 'hidden';
}
} else {
bodies.set(body, { counter: bodyInfo.counter + 1, initialOverflow: bodyInfo.initialOverflow });
}
@ -48,7 +74,16 @@ export default !doc
if (bodyInfo) {
if (bodyInfo.counter === 1) {
bodies.delete(body);
body.style.overflow = bodyInfo.initialOverflow;
if (isIosDevice) {
body.ontouchmove = null;
if (documentListenerAdded) {
document.removeEventListener('touchmove', preventDefault);
documentListenerAdded = false;
}
} else {
body.style.overflow = bodyInfo.initialOverflow;
}
} else {
bodies.set(body, { counter: bodyInfo.counter - 1, initialOverflow: bodyInfo.initialOverflow });
}

View File

@ -1,11 +1,22 @@
import { useRef } from 'react';
function useTitle(title: string) {
const t = useRef<string>();
if (t.current !== title) {
document.title = t.current = title;
}
import { useRef, useEffect } from 'react';
export interface UseTitleOptions {
restoreOnUnmount?: boolean;
}
const DEFAULT_USE_TITLE_OPTIONS: UseTitleOptions = {
restoreOnUnmount: false,
};
function useTitle(title: string, options: UseTitleOptions = DEFAULT_USE_TITLE_OPTIONS) {
const prevTitleRef = useRef(document.title);
document.title = title;
useEffect(() => {
if (options && options.restoreOnUnmount) {
return () => {
document.title = prevTitleRef.current;
};
} else {
return;
}
}, []);
}
export default typeof document !== 'undefined' ? useTitle : (_title: string) => {};

View File

@ -0,0 +1,31 @@
import { storiesOf } from "@storybook/react";
import React, { useState, useEffect } from "react";
import { useCookie } from "../src";
import ShowDocs from "./util/ShowDocs";
const Demo = () => {
const [value, updateCookie, deleteCookie] = useCookie("my-cookie");
const [counter, setCounter] = useState(1);
useEffect(() => {
deleteCookie();
}, []);
const updateCookieHandler = () => {
updateCookie(`my-awesome-cookie-${counter}`);
setCounter(c => c + 1);
};
return (
<div>
<p>Value: {value}</p>
<button onClick={updateCookieHandler}>Update Cookie</button>
<br />
<button onClick={deleteCookie}>Delete Cookie</button>
</div>
);
};
storiesOf("Side effects|useCookie", module)
.add("Docs", () => <ShowDocs md={require("../docs/useCookie.md")} />)
.add("Demo", () => <Demo />);

View File

@ -0,0 +1,46 @@
import { storiesOf } from '@storybook/react';
import React from 'react';
import { useError } from '../src';
import ShowDocs from './util/ShowDocs';
class ErrorBoundary extends React.Component<{}, { hasError: boolean }> {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return (
<div>
<h1>Something went wrong.</h1>
<button onClick={() => this.setState({ hasError: false })}>Retry</button>
</div>
);
}
return this.props.children;
}
}
const Demo = () => {
const dispatchError = useError();
const clickHandler = () => {
dispatchError(new Error('Some error!'));
};
return <button onClick={clickHandler}>Click me to throw</button>;
};
storiesOf('Side effects|useError', module)
.add('Docs', () => <ShowDocs md={require('../docs/useLocalStorage.md')} />)
.add('Demo', () => (
<ErrorBoundary>
<Demo />
</ErrorBoundary>
));

1
tests/setupTests.ts Normal file
View File

@ -0,0 +1 @@
import 'jest-localstorage-mock';

View File

@ -2,9 +2,6 @@ import { renderHook } from '@testing-library/react-hooks';
import { useCallback } from 'react';
import useAsync from '../src/useAsync';
// NOTE: these tests cause console errors.
// maybe we should test in a real environment instead
// of a fake one?
describe('useAsync', () => {
it('should be defined', () => {
expect(useAsync).toBeDefined();
@ -34,8 +31,9 @@ describe('useAsync', () => {
});
});
it('initially starts loading', () => {
it('initially starts loading', async () => {
expect(hook.result.current.loading).toEqual(true);
await hook.waitForNextUpdate();
});
it('resolves', async () => {
@ -75,8 +73,9 @@ describe('useAsync', () => {
});
});
it('initially starts loading', () => {
it('initially starts loading', async () => {
expect(hook.result.current.loading).toBeTruthy();
await hook.waitForNextUpdate();
});
it('resolves', async () => {

68
tests/useCookie.test.tsx Normal file
View File

@ -0,0 +1,68 @@
import { renderHook, act } from '@testing-library/react-hooks';
import Cookies from 'js-cookie';
import { useCookie } from '../src';
const setup = (cookieName: string) => renderHook(() => useCookie(cookieName));
it('should have initial value of null if no cookie exists', () => {
const { result } = setup('some-cookie');
expect(result.current[0]).toBeNull();
});
it('should have initial value of the cookie if it exists', () => {
const cookieName = 'some-cookie';
const value = 'some-value';
Cookies.set(cookieName, value);
const { result } = setup(cookieName);
expect(result.current[0]).toBe(value);
// cleanup
Cookies.remove(cookieName);
});
it('should update the cookie on call to updateCookie', () => {
const spy = jest.spyOn(Cookies, 'set');
const cookieName = 'some-cookie';
const { result } = setup(cookieName);
const newValue = 'some-new-value';
act(() => {
result.current[1](newValue);
});
expect(result.current[0]).toBe(newValue);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(cookieName, newValue, undefined);
// cleanup
spy.mockRestore();
Cookies.remove(cookieName);
});
it('should delete the cookie on call to deleteCookie', () => {
const cookieName = 'some-cookie';
const value = 'some-value';
Cookies.set(cookieName, value);
const spy = jest.spyOn(Cookies, 'remove');
const { result } = setup(cookieName);
expect(result.current[0]).toBe(value);
act(() => {
result.current[2]();
});
expect(result.current[0]).toBeNull();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenLastCalledWith(cookieName);
// cleanup
spy.mockRestore();
Cookies.remove(cookieName);
});

26
tests/useError.test.ts Normal file
View File

@ -0,0 +1,26 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { useError } from '../src';
const setup = () => renderHook(() => useError());
beforeEach(() => {
jest.spyOn(console, 'error').mockImplementation(() => {});
});
afterEach(() => {
jest.clearAllMocks();
});
it('should throw an error on error dispatch', () => {
const errorStr = 'some_error';
try {
const { result } = setup();
act(() => {
result.current(new Error(errorStr));
});
} catch (err) {
expect(err.message).toEqual(errorStr);
}
});

View File

@ -0,0 +1,95 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { useLocalStorage } from '../src';
const STRINGIFIED_VALUE = '{"a":"b"}';
const JSONIFIED_VALUE = { a: 'b' };
afterEach(() => {
localStorage.clear();
jest.clearAllMocks();
});
it('should return undefined if no initialValue provided and localStorage empty', () => {
const { result } = renderHook(() => useLocalStorage('some_key'));
expect(result.current[0]).toBeUndefined();
});
it('should set the value from existing localStorage key', () => {
const key = 'some_key';
localStorage.setItem(key, STRINGIFIED_VALUE);
const { result } = renderHook(() => useLocalStorage(key));
expect(result.current[0]).toEqual(JSONIFIED_VALUE);
});
it('should return initialValue if localStorage empty and set that to localStorage', () => {
const key = 'some_key';
const value = 'some_value';
const { result } = renderHook(() => useLocalStorage(key, value));
expect(result.current[0]).toBe(value);
expect(localStorage.__STORE__[key]).toBe(`"${value}"`);
});
it('should return the value from localStorage if exists even if initialValue provied', () => {
const key = 'some_key';
localStorage.setItem(key, STRINGIFIED_VALUE);
const { result } = renderHook(() => useLocalStorage(key, 'random_value'));
expect(result.current[0]).toEqual(JSONIFIED_VALUE);
});
it('should properly update the localStorage on change', () => {
const key = 'some_key';
const updatedValue = { b: 'a' };
const expectedValue = '{"b":"a"}';
const { result } = renderHook(() => useLocalStorage(key));
act(() => {
result.current[1](updatedValue);
});
expect(result.current[0]).toBe(updatedValue);
expect(localStorage.__STORE__[key]).toBe(expectedValue);
});
describe('Options with raw true', () => {
it('should set the value from existing localStorage key', () => {
const key = 'some_key';
localStorage.setItem(key, STRINGIFIED_VALUE);
const { result } = renderHook(() => useLocalStorage(key, '', { raw: true }));
expect(result.current[0]).toEqual(STRINGIFIED_VALUE);
});
it('should return initialValue if localStorage empty and set that to localStorage', () => {
const key = 'some_key';
const { result } = renderHook(() => useLocalStorage(key, STRINGIFIED_VALUE, { raw: true }));
expect(result.current[0]).toBe(STRINGIFIED_VALUE);
expect(localStorage.__STORE__[key]).toBe(STRINGIFIED_VALUE);
});
});
describe('Options with raw false and provided serializer/deserializer', () => {
const serializer = (_: string) => '321';
const deserializer = (_: string) => '123';
it('should return valid serialized value from existing localStorage key', () => {
const key = 'some_key';
localStorage.setItem(key, STRINGIFIED_VALUE);
const { result } = renderHook(() =>
useLocalStorage(key, STRINGIFIED_VALUE, { raw: false, serializer, deserializer })
);
expect(result.current[0]).toBe('123');
});
});

View File

@ -13,4 +13,16 @@ describe('useTitle', () => {
hook.rerender('My other page title');
expect(document.title).toBe('My other page title');
});
it('should restore document title on unmount', () => {
renderHook(props => useTitle(props), { initialProps: 'Old Title' });
expect(document.title).toBe('Old Title');
const hook = renderHook(props => useTitle(props.title, { restoreOnUnmount: props.restore }), {
initialProps: { title: 'New Title', restore: true },
});
expect(document.title).toBe('New Title');
hook.unmount();
expect(document.title).toBe('Old Title');
});
});

1384
yarn.lock

File diff suppressed because it is too large Load Diff