diff --git a/docs/useHash.md b/docs/useHash.md new file mode 100644 index 0000000000..7cb4092b6f --- /dev/null +++ b/docs/useHash.md @@ -0,0 +1,39 @@ +# `useHash` + +React sensor hook that tracks browser's location hash. + +## Usage + +```jsx +import {useHash} from 'react-use'; + +const Demo = () => { + const [hash, setHash] = useHash(); + + useMount(() => { + setHash('#/path/to/page?userId=123'); + }); + + return ( +
+
window.location.href:
+
+
{window.location.href}
+
+
Edit hash:
+
+ setHash(e.target.value)} /> +
+
+ ); +}; +``` + +## API + +`const [hash, setHash] = useHash()` + +Get latest url hash with `hash` and set url hash with `setHash`. + +- `hash: string`: get current url hash. listen to `hashchange` event. +- `setHash: (newHash: string) => void`: change url hash. Invoke this method will trigger `hashchange` event. \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 70e290a965..6d0181648b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -110,3 +110,4 @@ export { useRendersCount } from './useRendersCount'; export { useFirstMountState } from './useFirstMountState'; export { default as useSet } from './useSet'; export { createGlobalState } from './createGlobalState'; +export { useHash } from './useHash' \ No newline at end of file diff --git a/src/useHash.ts b/src/useHash.ts new file mode 100644 index 0000000000..34d5dd7597 --- /dev/null +++ b/src/useHash.ts @@ -0,0 +1,27 @@ +import { useState, useCallback } from "react" +import useLifecycles from "./useLifecycles" + +/** + * read and write url hash, response to url hash change + */ +export const useHash = () => { + const [hash, setHash] = useState(() => window.location.hash) + + const onHashChange = useCallback(() => { + setHash(window.location.hash) + }, []) + + useLifecycles(() => { + window.addEventListener('hashchange', onHashChange) + }, () => { + window.removeEventListener('hashchange', onHashChange) + }) + + const _setHash = useCallback((newHash: string) => { + if (newHash !== hash) { + window.location.hash = newHash + } + }, [hash]) + + return [hash, _setHash] as const +} \ No newline at end of file diff --git a/stories/useHash.story.tsx b/stories/useHash.story.tsx new file mode 100644 index 0000000000..ad21c82329 --- /dev/null +++ b/stories/useHash.story.tsx @@ -0,0 +1,29 @@ +import { storiesOf } from '@storybook/react'; +import * as React from 'react'; +import { useHash, useMount } from '../src'; +import ShowDocs from './util/ShowDocs'; + +const Demo = () => { + const [hash, setHash] = useHash(); + + useMount(() => { + setHash('#/path/to/page?userId=123'); + }); + + return ( +
+
window.location.href:
+
+
{window.location.href}
+
+
Edit hash:
+
+ setHash(e.target.value)} /> +
+
+ ); +}; + +storiesOf('Sensors|useHash', module) + .add('Docs', () => ) + .add('Demo', () => ); diff --git a/tests/useHash.test.ts b/tests/useHash.test.ts new file mode 100644 index 0000000000..d097538a49 --- /dev/null +++ b/tests/useHash.test.ts @@ -0,0 +1,54 @@ +import { renderHook, act } from '@testing-library/react-hooks'; +import { useHash } from '../src/useHash'; + +(global as any).window = Object.create(window); +let mockHash = '#'; +const mockLocation = {}; +Object.defineProperty(mockLocation, 'hash', { + get() { + return mockHash; + }, + set(newHash) { + mockHash = newHash; + window.dispatchEvent(new HashChangeEvent('hashchange')); + }, +}); +Object.defineProperty(window, 'location', { + value: mockLocation, +}); + +beforeEach(() => { + window.location.hash = '#'; +}); + +test('returns current url hash', () => { + window.location.hash = '#abc'; + + const { result } = renderHook(() => useHash()); + + const hash = result.current[0]; + expect(hash).toBe('#abc'); +}); + +test('returns latest url hash when change the hash with setHash', () => { + const { result } = renderHook(() => useHash()); + const hash = result.current[0]; + const setHash = result.current[1]; + expect(hash).toBe('#'); + act(() => { + setHash('#abc'); + }); + const hash2 = result.current[0]; + expect(hash2).toBe('#abc'); +}); + +it('returns latest url hash when change the hash with "hashchange" event', () => { + const {result} = renderHook(() => useHash()); + const hash = result.current[0] + expect(hash).toBe('#'); + act(() => { + window.location.hash = '#abc' + }) + const hash2 = result.current[0] + expect(hash2).toBe('#abc'); +}); \ No newline at end of file