Skip to content

Commit 44a6cde

Browse files
whincxobotyi
authored andcommitted
feat: add useHash hook
1 parent 437719f commit 44a6cde

File tree

5 files changed

+150
-0
lines changed

5 files changed

+150
-0
lines changed

Diff for: docs/useHash.md

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# `useHash`
2+
3+
React sensor hook that tracks browser's location hash.
4+
5+
## Usage
6+
7+
```jsx
8+
import {useHash} from 'react-use';
9+
10+
const Demo = () => {
11+
const [hash, setHash] = useHash();
12+
13+
useMount(() => {
14+
setHash('#/path/to/page?userId=123');
15+
});
16+
17+
return (
18+
<div>
19+
<div>window.location.href:</div>
20+
<div>
21+
<pre>{window.location.href}</pre>
22+
</div>
23+
<div>Edit hash: </div>
24+
<div>
25+
<input style={{ width: '100%' }} value={hash} onChange={e => setHash(e.target.value)} />
26+
</div>
27+
</div>
28+
);
29+
};
30+
```
31+
32+
## API
33+
34+
`const [hash, setHash] = useHash()`
35+
36+
Get latest url hash with `hash` and set url hash with `setHash`.
37+
38+
- `hash: string`: get current url hash. listen to `hashchange` event.
39+
- `setHash: (newHash: string) => void`: change url hash. Invoke this method will trigger `hashchange` event.

Diff for: src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,4 @@ export { useRendersCount } from './useRendersCount';
110110
export { useFirstMountState } from './useFirstMountState';
111111
export { default as useSet } from './useSet';
112112
export { createGlobalState } from './createGlobalState';
113+
export { useHash } from './useHash'

Diff for: src/useHash.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { useState, useCallback } from "react"
2+
import useLifecycles from "./useLifecycles"
3+
4+
/**
5+
* read and write url hash, response to url hash change
6+
*/
7+
export const useHash = () => {
8+
const [hash, setHash] = useState(() => window.location.hash)
9+
10+
const onHashChange = useCallback(() => {
11+
setHash(window.location.hash)
12+
}, [])
13+
14+
useLifecycles(() => {
15+
window.addEventListener('hashchange', onHashChange)
16+
}, () => {
17+
window.removeEventListener('hashchange', onHashChange)
18+
})
19+
20+
const _setHash = useCallback((newHash: string) => {
21+
if (newHash !== hash) {
22+
window.location.hash = newHash
23+
}
24+
}, [hash])
25+
26+
return [hash, _setHash] as const
27+
}

Diff for: stories/useHash.story.tsx

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { storiesOf } from '@storybook/react';
2+
import * as React from 'react';
3+
import { useHash, useMount } from '../src';
4+
import ShowDocs from './util/ShowDocs';
5+
6+
const Demo = () => {
7+
const [hash, setHash] = useHash();
8+
9+
useMount(() => {
10+
setHash('#/path/to/page?userId=123');
11+
});
12+
13+
return (
14+
<div>
15+
<div>window.location.href:</div>
16+
<div>
17+
<pre>{window.location.href}</pre>
18+
</div>
19+
<div>Edit hash: </div>
20+
<div>
21+
<input style={{ width: '100%' }} value={hash} onChange={e => setHash(e.target.value)} />
22+
</div>
23+
</div>
24+
);
25+
};
26+
27+
storiesOf('Sensors|useHash', module)
28+
.add('Docs', () => <ShowDocs md={require('../docs/useHash.md')} />)
29+
.add('Demo', () => <Demo />);

Diff for: tests/useHash.test.ts

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { renderHook, act } from '@testing-library/react-hooks';
2+
import { useHash } from '../src/useHash';
3+
4+
(global as any).window = Object.create(window);
5+
let mockHash = '#';
6+
const mockLocation = {};
7+
Object.defineProperty(mockLocation, 'hash', {
8+
get() {
9+
return mockHash;
10+
},
11+
set(newHash) {
12+
mockHash = newHash;
13+
window.dispatchEvent(new HashChangeEvent('hashchange'));
14+
},
15+
});
16+
Object.defineProperty(window, 'location', {
17+
value: mockLocation,
18+
});
19+
20+
beforeEach(() => {
21+
window.location.hash = '#';
22+
});
23+
24+
test('returns current url hash', () => {
25+
window.location.hash = '#abc';
26+
27+
const { result } = renderHook(() => useHash());
28+
29+
const hash = result.current[0];
30+
expect(hash).toBe('#abc');
31+
});
32+
33+
test('returns latest url hash when change the hash with setHash', () => {
34+
const { result } = renderHook(() => useHash());
35+
const hash = result.current[0];
36+
const setHash = result.current[1];
37+
expect(hash).toBe('#');
38+
act(() => {
39+
setHash('#abc');
40+
});
41+
const hash2 = result.current[0];
42+
expect(hash2).toBe('#abc');
43+
});
44+
45+
it('returns latest url hash when change the hash with "hashchange" event', () => {
46+
const {result} = renderHook(() => useHash());
47+
const hash = result.current[0]
48+
expect(hash).toBe('#');
49+
act(() => {
50+
window.location.hash = '#abc'
51+
})
52+
const hash2 = result.current[0]
53+
expect(hash2).toBe('#abc');
54+
});

0 commit comments

Comments
 (0)