mirror of
https://github.com/streamich/react-use.git
synced 2026-01-18 14:06:52 +00:00
feat: refactor the useNetwork hook.
Improved hook performance, now works with safari. BREAKING CHANGE: `useNetwork` hook renamed to `useNetworkState`.
This commit is contained in:
parent
723c588fef
commit
23037f207d
@ -24,8 +24,8 @@ ij_typescript_catch_on_new_line = false
|
||||
ij_typescript_spaces_within_interpolation_expressions = false
|
||||
|
||||
[*.md]
|
||||
max_line_length = 0
|
||||
max_line_length = 120
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[COMMIT_EDITMSG]
|
||||
max_line_length = 0
|
||||
max_line_length = 80
|
||||
|
||||
@ -58,7 +58,7 @@
|
||||
- [`useMotion`](./docs/useMotion.md) — tracks state of device's motion sensor.
|
||||
- [`useMouse` and `useMouseHovered`](./docs/useMouse.md) — tracks state of mouse position. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usemouse--docs)
|
||||
- [`useMouseWheel`](./docs/useMouseWheel.md) — tracks deltaY of scrolled mouse wheel. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usemousewheel--docs)
|
||||
- [`useNetwork`](./docs/useNetwork.md) — tracks state of user's internet connection.
|
||||
- [`useNetworkState`](./docs/useNetworkState.md) — tracks the state of browser's network connection. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usenetworkstate--demo)
|
||||
- [`useOrientation`](./docs/useOrientation.md) — tracks state of device's screen orientation.
|
||||
- [`usePageLeave`](./docs/usePageLeave.md) — triggers when mouse leaves page boundaries.
|
||||
- [`useScratch`](./docs/useScratch.md) — tracks mouse click-and-scrub state.
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
# `useNetwork`
|
||||
|
||||
React sensor hook that tracks connected hardware devices. Returns:
|
||||
|
||||
```json
|
||||
{
|
||||
"online": true,
|
||||
"since": "2018-10-27T08:59:05.562Z",
|
||||
"downlink": 10,
|
||||
"effectiveType": "4g",
|
||||
"rtt": 50
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```jsx
|
||||
import {useNetwork} from 'react-use';
|
||||
|
||||
const Demo = () => {
|
||||
const state = useNetwork();
|
||||
|
||||
return (
|
||||
<pre>
|
||||
{JSON.stringify(state, null, 2)}
|
||||
</pre>
|
||||
);
|
||||
};
|
||||
```
|
||||
77
docs/useNetworkState.md
Normal file
77
docs/useNetworkState.md
Normal file
@ -0,0 +1,77 @@
|
||||
# `useNetworkState`
|
||||
|
||||
Tracks the state of browser's network connection.
|
||||
|
||||
As of the standard it is not guaranteed that browser connected to the _Internet_, it only guarantees the network
|
||||
connection.
|
||||
|
||||
## Usage
|
||||
|
||||
```jsx
|
||||
import {useNetworkState} from 'react-use';
|
||||
|
||||
const Demo = () => {
|
||||
const state = useNetworkState();
|
||||
|
||||
return (
|
||||
<pre>
|
||||
{JSON.stringify(state, null, 2)}
|
||||
</pre>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
#### State interface:
|
||||
|
||||
```typescript
|
||||
interface IUseNetworkState {
|
||||
/**
|
||||
* @desc Whether browser connected to the network or not.
|
||||
*/
|
||||
online: boolean | undefined;
|
||||
/**
|
||||
* @desc Previous value of `online` property. Helps to identify if browser
|
||||
* just connected or lost connection.
|
||||
*/
|
||||
previous: boolean | undefined;
|
||||
/**
|
||||
* @desc The {Date} object pointing to the moment when state change occurred.
|
||||
*/
|
||||
since: Date | undefined;
|
||||
/**
|
||||
* @desc Effective bandwidth estimate in megabits per second, rounded to the
|
||||
* nearest multiple of 25 kilobits per seconds.
|
||||
*/
|
||||
downlink: number | undefined;
|
||||
/**
|
||||
* @desc Maximum downlink speed, in megabits per second (Mbps), for the
|
||||
* underlying connection technology
|
||||
*/
|
||||
downlinkMax: number | undefined;
|
||||
/**
|
||||
* @desc Effective type of the connection meaning one of 'slow-2g', '2g', '3g', or '4g'.
|
||||
* This value is determined using a combination of recently observed round-trip time
|
||||
* and downlink values.
|
||||
*/
|
||||
effectiveType: 'slow-2g' | '2g' | '3g' | '4g' | undefined;
|
||||
/**
|
||||
* @desc Estimated effective round-trip time of the current connection, rounded
|
||||
* to the nearest multiple of 25 milliseconds
|
||||
*/
|
||||
rtt: number | undefined;
|
||||
/**
|
||||
* @desc Wheter user has set a reduced data usage option on the user agent.
|
||||
*/
|
||||
saveData: boolen | undefined;
|
||||
/**
|
||||
* @desc The type of connection a device is using to communicate with the network.
|
||||
*/
|
||||
type: 'bluetooth' | 'cellular' | 'ethernet' | 'none' | 'wifi' | 'wimax' | 'other' | 'unknown' | undefined;
|
||||
}
|
||||
```
|
||||
|
||||
#### Call signature
|
||||
|
||||
```typescript
|
||||
function useNetworkState(initialState?: IUseNetworkState | (() => IUseNetworkState)): IUseNetworkState;
|
||||
```
|
||||
@ -61,7 +61,7 @@ export { default as useMountedState } from './useMountedState';
|
||||
export { default as useMouse } from './useMouse';
|
||||
export { default as useMouseHovered } from './useMouseHovered';
|
||||
export { default as useMouseWheel } from './useMouseWheel';
|
||||
export { default as useNetwork } from './useNetwork';
|
||||
export { default as useNetworkState } from './useNetworkState';
|
||||
export { default as useNumber } from './useNumber';
|
||||
export { default as useObservable } from './useObservable';
|
||||
export { default as useOrientation } from './useOrientation';
|
||||
|
||||
@ -1,88 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { off, on } from './misc/util';
|
||||
|
||||
export interface NetworkState {
|
||||
online?: boolean;
|
||||
since?: Date;
|
||||
downlink?: number;
|
||||
downlinkMax?: number;
|
||||
effectiveType?: string;
|
||||
rtt?: number;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
const getConnection = () => {
|
||||
if (typeof navigator !== 'object') {
|
||||
return null;
|
||||
}
|
||||
const nav = navigator as any;
|
||||
return nav.connection || nav.mozConnection || nav.webkitConnection;
|
||||
};
|
||||
|
||||
const getConnectionState = (): NetworkState => {
|
||||
const connection = getConnection();
|
||||
if (!connection) {
|
||||
return {};
|
||||
}
|
||||
const { downlink, downlinkMax, effectiveType, type, rtt } = connection;
|
||||
return {
|
||||
downlink,
|
||||
downlinkMax,
|
||||
effectiveType,
|
||||
type,
|
||||
rtt,
|
||||
};
|
||||
};
|
||||
|
||||
const useNetwork = (initialState: NetworkState = {}) => {
|
||||
const [state, setState] = useState(initialState);
|
||||
|
||||
useEffect(() => {
|
||||
let localState = state;
|
||||
const localSetState = (patch) => {
|
||||
localState = { ...localState, ...patch };
|
||||
setState(localState);
|
||||
};
|
||||
const connection = getConnection();
|
||||
|
||||
const onOnline = () => {
|
||||
localSetState({
|
||||
online: true,
|
||||
since: new Date(),
|
||||
});
|
||||
};
|
||||
const onOffline = () => {
|
||||
localSetState({
|
||||
online: false,
|
||||
since: new Date(),
|
||||
});
|
||||
};
|
||||
const onConnectionChange = () => {
|
||||
localSetState(getConnectionState());
|
||||
};
|
||||
|
||||
on(window, 'online', onOnline);
|
||||
on(window, 'offline', onOffline);
|
||||
if (connection) {
|
||||
on(connection, 'change', onConnectionChange);
|
||||
localSetState({
|
||||
...state,
|
||||
online: navigator.onLine,
|
||||
since: undefined,
|
||||
...getConnectionState(),
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
off(window, 'online', onOnline);
|
||||
off(window, 'offline', onOffline);
|
||||
if (connection) {
|
||||
off(connection, 'change', onConnectionChange);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
export default useNetwork;
|
||||
118
src/useNetworkState.ts
Normal file
118
src/useNetworkState.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { off, on } from './misc/util';
|
||||
import { IHookStateInitAction } from './misc/hookState';
|
||||
|
||||
export interface INetworkInformation extends EventTarget {
|
||||
readonly downlink: number;
|
||||
readonly downlinkMax: number;
|
||||
readonly effectiveType: 'slow-2g' | '2g' | '3g' | '4g';
|
||||
readonly rtt: number;
|
||||
readonly saveData: boolean;
|
||||
readonly type: 'bluetooth' | 'cellular' | 'ethernet' | 'none' | 'wifi' | 'wimax' | 'other' | 'unknown';
|
||||
|
||||
onChange: (event: Event) => void;
|
||||
}
|
||||
|
||||
export interface IUseNetworkState {
|
||||
/**
|
||||
* @desc Whether browser connected to the network or not.
|
||||
*/
|
||||
online: boolean | undefined;
|
||||
/**
|
||||
* @desc Previous value of `online` property. Helps to identify if browser
|
||||
* just connected or lost connection.
|
||||
*/
|
||||
previous: boolean | undefined;
|
||||
/**
|
||||
* @desc The {Date} object pointing to the moment when state change occurred.
|
||||
*/
|
||||
since: Date | undefined;
|
||||
/**
|
||||
* @desc Effective bandwidth estimate in megabits per second, rounded to the
|
||||
* nearest multiple of 25 kilobits per seconds.
|
||||
*/
|
||||
downlink: INetworkInformation['downlink'] | undefined;
|
||||
/**
|
||||
* @desc Maximum downlink speed, in megabits per second (Mbps), for the
|
||||
* underlying connection technology
|
||||
*/
|
||||
downlinkMax: INetworkInformation['downlinkMax'] | undefined;
|
||||
/**
|
||||
* @desc Effective type of the connection meaning one of 'slow-2g', '2g', '3g', or '4g'.
|
||||
* This value is determined using a combination of recently observed round-trip time
|
||||
* and downlink values.
|
||||
*/
|
||||
effectiveType: INetworkInformation['effectiveType'] | undefined;
|
||||
/**
|
||||
* @desc Estimated effective round-trip time of the current connection, rounded
|
||||
* to the nearest multiple of 25 milliseconds
|
||||
*/
|
||||
rtt: INetworkInformation['rtt'] | undefined;
|
||||
/**
|
||||
* @desc {true} if the user has set a reduced data usage option on the user agent.
|
||||
*/
|
||||
saveData: INetworkInformation['saveData'] | undefined;
|
||||
/**
|
||||
* @desc The type of connection a device is using to communicate with the network.
|
||||
* It will be one of the following values:
|
||||
* - bluetooth
|
||||
* - cellular
|
||||
* - ethernet
|
||||
* - none
|
||||
* - wifi
|
||||
* - wimax
|
||||
* - other
|
||||
* - unknown
|
||||
*/
|
||||
type: INetworkInformation['type'] | undefined;
|
||||
}
|
||||
|
||||
const nav:
|
||||
| (Navigator & Partial<Record<'connection' | 'mozConnection' | 'webkitConnection', INetworkInformation>>)
|
||||
| undefined = navigator;
|
||||
const conn: INetworkInformation | undefined = nav && (nav.connection || nav.mozConnection || nav.webkitConnection);
|
||||
|
||||
function getConnectionState(previousState?: IUseNetworkState): IUseNetworkState {
|
||||
const online = nav?.onLine;
|
||||
const previousOnline = previousState?.online;
|
||||
|
||||
return {
|
||||
online,
|
||||
previous: previousOnline,
|
||||
since: online !== previousOnline ? new Date() : previousState?.since,
|
||||
downlink: conn?.downlink,
|
||||
downlinkMax: conn?.downlinkMax,
|
||||
effectiveType: conn?.effectiveType,
|
||||
rtt: conn?.rtt,
|
||||
saveData: conn?.saveData,
|
||||
type: conn?.type,
|
||||
};
|
||||
}
|
||||
|
||||
export default function useNetworkState(initialState?: IHookStateInitAction<IUseNetworkState>): IUseNetworkState {
|
||||
const [state, setState] = useState(initialState ?? getConnectionState);
|
||||
|
||||
useEffect(() => {
|
||||
const handleStateChange = () => {
|
||||
setState(getConnectionState);
|
||||
};
|
||||
|
||||
on(window, 'online', handleStateChange, { passive: true });
|
||||
on(window, 'offline', handleStateChange, { passive: true });
|
||||
|
||||
if (conn) {
|
||||
on(conn, 'change', handleStateChange, { passive: true });
|
||||
}
|
||||
|
||||
return () => {
|
||||
off(window, 'online', handleStateChange);
|
||||
off(window, 'offline', handleStateChange);
|
||||
|
||||
if (conn) {
|
||||
off(conn, 'change', handleStateChange);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return state;
|
||||
}
|
||||
@ -1,14 +1,22 @@
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import * as React from 'react';
|
||||
import { useNetwork } from '../src';
|
||||
import { useEffect } from 'react';
|
||||
import { useNetworkState } from '../src';
|
||||
import ShowDocs from './util/ShowDocs';
|
||||
|
||||
const Demo = () => {
|
||||
const state = useNetwork();
|
||||
const state = useNetworkState();
|
||||
|
||||
return <pre>{JSON.stringify(state, null, 2)}</pre>;
|
||||
useEffect(() => {
|
||||
console.log(state);
|
||||
}, [state])
|
||||
|
||||
return <div>
|
||||
<div>Since JSON do not output `undefined` fields look the console to see whole the state</div>
|
||||
<pre>{JSON.stringify(state, null, 2)}</pre>
|
||||
</div>;
|
||||
};
|
||||
|
||||
storiesOf('Sensors/useNetwork', module)
|
||||
.add('Docs', () => <ShowDocs md={require('../docs/useNetwork.md')} />)
|
||||
storiesOf('Sensors/useNetworkState', module)
|
||||
.add('Docs', () => <ShowDocs md={require('../docs/useNetworkState.md')} />)
|
||||
.add('Demo', () => <Demo />);
|
||||
|
||||
26
tests/useNetworkState.test.ts
Normal file
26
tests/useNetworkState.test.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { useNetworkState } from '../src';
|
||||
|
||||
//ToDo: improve tests
|
||||
describe(`useNetworkState`, () => {
|
||||
it('should be defined', () => {
|
||||
expect(useNetworkState).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return an object of certain structure', () => {
|
||||
const hook = renderHook(() => useNetworkState(), { initialProps: false });
|
||||
|
||||
expect(typeof hook.result.current).toEqual('object');
|
||||
expect(Object.keys(hook.result.current)).toEqual([
|
||||
'online',
|
||||
'previous',
|
||||
'since',
|
||||
'downlink',
|
||||
'downlinkMax',
|
||||
'effectiveType',
|
||||
'rtt',
|
||||
'saveData',
|
||||
'type',
|
||||
]);
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user