Skip to content

Commit

Permalink
chore: refactoring and rearrangement.
Browse files Browse the repository at this point in the history
More DRY code. Also move non-hooks to separate directories.

BREAKING CHANGE: all `create*` factories been moved to `factory` subdirectory and in case direct import should be imported like `react-use/esm/factory/createBreakpoint`
BREAKING CHANGE: `comps` directory renamed to `component`
  • Loading branch information
renovate-bot authored and xobotyi committed Jan 30, 2021
1 parent bbbe4d5 commit a27f09f
Show file tree
Hide file tree
Showing 96 changed files with 2,585 additions and 4,903 deletions.
2 changes: 1 addition & 1 deletion docs/useKey.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const Demo = () => {
Or as render-prop:

```jsx
import UseKey from 'react-use/lib/comps/UseKey';
import UseKey from 'react-use/lib/component/UseKey';

<UseKey filter='a' fn={() => alert('"a" key pressed!')} />
```
Expand Down
32 changes: 16 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,19 @@
},
"homepage": "https://github.com/streamich/react-use#readme",
"dependencies": {
"@types/js-cookie": "2.2.6",
"@xobotyi/scrollbar-width": "1.9.5",
"copy-to-clipboard": "^3.2.0",
"@xobotyi/scrollbar-width": "^1.9.5",
"copy-to-clipboard": "^3.3.1",
"fast-deep-equal": "^3.1.3",
"fast-shallow-equal": "^1.0.0",
"js-cookie": "^2.2.1",
"nano-css": "^5.2.1",
"nano-css": "^5.3.1",
"react-universal-interface": "^0.6.2",
"resize-observer-polyfill": "^1.5.1",
"screenfull": "^5.0.0",
"screenfull": "^5.1.0",
"set-harmonic-interval": "^1.0.1",
"throttle-debounce": "^2.1.0",
"throttle-debounce": "^3.0.1",
"ts-easing": "^0.2.0",
"tslib": "^2.0.0"
"tslib": "^2.1.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0",
Expand All @@ -82,23 +81,24 @@
"@storybook/addon-options": "5.3.21",
"@storybook/react": "6.1.15",
"@testing-library/react": "11.2.3",
"@testing-library/react-hooks": "3.7.0",
"@testing-library/react-hooks": "5.0.3",
"@types/jest": "26.0.20",
"@types/react": "16.9.11",
"@types/js-cookie": "2.2.6",
"@types/react": "17.0.0",
"@typescript-eslint/eslint-plugin": "4.14.1",
"@typescript-eslint/parser": "4.14.1",
"babel-core": "6.26.3",
"babel-eslint": "10.1.0",
"babel-loader": "8.2.2",
"babel-plugin-dynamic-import-node": "2.3.3",
"eslint": "7.18.0",
"eslint-config-react-app": "5.2.1",
"eslint-config-react-app": "6.0.0",
"eslint-plugin-flowtype": "5.2.0",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "4.2.0",
"fork-ts-checker-webpack-plugin": "5.2.1",
"fork-ts-checker-webpack-plugin": "6.1.0",
"gh-pages": "3.1.0",
"husky": "4.3.8",
"jest": "26.6.3",
Expand All @@ -108,11 +108,11 @@
"markdown-loader": "6.0.0",
"prettier": "2.2.1",
"raf-stub": "3.0.0",
"react": "16.14.0",
"react-dom": "16.14.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-frame-component": "4.1.3",
"react-spring": "8.0.27",
"react-test-renderer": "16.14.0",
"react-test-renderer": "17.0.1",
"rebound": "0.1.0",
"redux-logger": "3.0.6",
"redux-thunk": "2.3.0",
Expand All @@ -122,7 +122,7 @@
"ts-jest": "26.5.0",
"ts-loader": "8.0.14",
"ts-node": "9.1.1",
"typescript": "3.9.7"
"typescript": "4.1.3"
},
"config": {
"commitizen": {
Expand Down Expand Up @@ -156,7 +156,7 @@
]
},
"volta": {
"node": "10.23.2",
"node": "10.23.1",
"yarn": "1.22.10"
},
"collective": {
Expand Down
2 changes: 1 addition & 1 deletion src/comps/UseKey.tsx β†’ src/component/UseKey.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import useKey from '../useKey';
import createRenderProp from '../util/createRenderProp';
import createRenderProp from '../factory/createRenderProp';

const UseKey = createRenderProp(useKey, ({ filter, fn, deps, ...rest }) => [filter, fn, rest, deps]);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect, useState, useMemo } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { off, on } from '../misc/util';

const createBreakpoint = (
breakpoints: { [name: string]: number } = { laptopL: 1440, laptop: 1024, tablet: 768 }
Expand All @@ -10,9 +11,9 @@ const createBreakpoint = (
setScreen(window.innerWidth);
};
setSideScreen();
window.addEventListener('resize', setSideScreen);
on(window, 'resize', setSideScreen);
return () => {
window.removeEventListener('resize', setSideScreen);
off(window, 'resize', setSideScreen);
};
});
const sortedBreakpoints = useMemo(() => Object.entries(breakpoints).sort((a, b) => (a[1] >= b[1] ? 1 : -1)), [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from 'react';
import useEffectOnce from './useEffectOnce';
import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect';
import useEffectOnce from '../useEffectOnce';
import useIsomorphicLayoutEffect from '../useIsomorphicLayoutEffect';

export function createGlobalState<S = any>(initialState?: S) {
const store: { state: S | undefined; setState: (state: S) => void; setters: any[] } = {
Expand Down
235 changes: 235 additions & 0 deletions src/factory/createHTMLMediaHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import * as React from 'react';
import { useEffect, useRef } from 'react';
import useSetState from '../useSetState';
import parseTimeRanges from '../misc/parseTimeRanges';

export interface HTMLMediaProps extends React.AudioHTMLAttributes<any>, React.VideoHTMLAttributes<any> {
src: string;
}

export interface HTMLMediaState {
buffered: any[];
duration: number;
paused: boolean;
muted: boolean;
time: number;
volume: number;
}

export interface HTMLMediaControls {
play: () => Promise<void> | void;
pause: () => void;
mute: () => void;
unmute: () => void;
volume: (volume: number) => void;
seek: (time: number) => void;
}

type createHTMLMediaHookReturn = [
React.ReactElement<HTMLMediaProps>,
HTMLMediaState,
HTMLMediaControls,
{ current: HTMLAudioElement | null }
];

export default function createHTMLMediaHook(tag: 'audio' | 'video') {
return (elOrProps: HTMLMediaProps | React.ReactElement<HTMLMediaProps>): createHTMLMediaHookReturn => {
let element: React.ReactElement<any> | undefined;
let props: HTMLMediaProps;

if (React.isValidElement(elOrProps)) {
element = elOrProps;
props = element.props;
} else {
props = elOrProps as HTMLMediaProps;
}

const [state, setState] = useSetState<HTMLMediaState>({
buffered: [],
time: 0,
duration: 0,
paused: true,
muted: false,
volume: 1,
});
const ref = useRef<HTMLAudioElement | null>(null);

const wrapEvent = (userEvent, proxyEvent?) => {
return (event) => {
try {
proxyEvent && proxyEvent(event);
} finally {
userEvent && userEvent(event);
}
};
};

const onPlay = () => setState({ paused: false });
const onPause = () => setState({ paused: true });
const onVolumeChange = () => {
const el = ref.current;
if (!el) {
return;
}
setState({
muted: el.muted,
volume: el.volume,
});
};
const onDurationChange = () => {
const el = ref.current;
if (!el) {
return;
}
const { duration, buffered } = el;
setState({
duration,
buffered: parseTimeRanges(buffered),
});
};
const onTimeUpdate = () => {
const el = ref.current;
if (!el) {
return;
}
setState({ time: el.currentTime });
};
const onProgress = () => {
const el = ref.current;
if (!el) {
return;
}
setState({ buffered: parseTimeRanges(el.buffered) });
};

if (element) {
element = React.cloneElement(element, {
controls: false,
...props,
ref,
onPlay: wrapEvent(props.onPlay, onPlay),
onPause: wrapEvent(props.onPause, onPause),
onVolumeChange: wrapEvent(props.onVolumeChange, onVolumeChange),
onDurationChange: wrapEvent(props.onDurationChange, onDurationChange),
onTimeUpdate: wrapEvent(props.onTimeUpdate, onTimeUpdate),
onProgress: wrapEvent(props.onProgress, onProgress),
});
} else {
element = React.createElement(tag, {
controls: false,
...props,
ref,
onPlay: wrapEvent(props.onPlay, onPlay),
onPause: wrapEvent(props.onPause, onPause),
onVolumeChange: wrapEvent(props.onVolumeChange, onVolumeChange),
onDurationChange: wrapEvent(props.onDurationChange, onDurationChange),
onTimeUpdate: wrapEvent(props.onTimeUpdate, onTimeUpdate),
onProgress: wrapEvent(props.onProgress, onProgress),
} as any); // TODO: fix this typing.
}

// Some browsers return `Promise` on `.play()` and may throw errors
// if one tries to execute another `.play()` or `.pause()` while that
// promise is resolving. So we prevent that with this lock.
// See: https://bugs.chromium.org/p/chromium/issues/detail?id=593273
let lockPlay: boolean = false;

const controls = {
play: () => {
const el = ref.current;
if (!el) {
return undefined;
}

if (!lockPlay) {
const promise = el.play();
const isPromise = typeof promise === 'object';

if (isPromise) {
lockPlay = true;
const resetLock = () => {
lockPlay = false;
};
promise.then(resetLock, resetLock);
}

return promise;
}
return undefined;
},
pause: () => {
const el = ref.current;
if (el && !lockPlay) {
return el.pause();
}
},
seek: (time: number) => {
const el = ref.current;
if (!el || state.duration === undefined) {
return;
}
time = Math.min(state.duration, Math.max(0, time));
el.currentTime = time;
},
volume: (volume: number) => {
const el = ref.current;
if (!el) {
return;
}
volume = Math.min(1, Math.max(0, volume));
el.volume = volume;
setState({ volume });
},
mute: () => {
const el = ref.current;
if (!el) {
return;
}
el.muted = true;
},
unmute: () => {
const el = ref.current;
if (!el) {
return;
}
el.muted = false;
},
};

useEffect(() => {
const el = ref.current!;

if (!el) {
if (process.env.NODE_ENV !== 'production') {
if (tag === 'audio') {
console.error(
'useAudio() ref to <audio> element is empty at mount. ' +
'It seem you have not rendered the audio element, which it ' +
'returns as the first argument const [audio] = useAudio(...).'
);
} else if (tag === 'video') {
console.error(
'useVideo() ref to <video> element is empty at mount. ' +
'It seem you have not rendered the video element, which it ' +
'returns as the first argument const [video] = useVideo(...).'
);
}
}
return;
}

setState({
volume: el.volume,
muted: el.muted,
paused: el.paused,
});

// Start media, if autoPlay requested.
if (props.autoPlay && el.paused) {
controls.play();
}
}, [props.src]);

return [element, state, controls, ref];
};
}
File renamed without changes.
2 changes: 1 addition & 1 deletion src/createReducer.ts β†’ src/factory/createReducer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MutableRefObject, useCallback, useRef, useState } from 'react';
import useUpdateEffect from './useUpdateEffect';
import useUpdateEffect from '../useUpdateEffect';

type Dispatch<Action> = (action: Action) => void;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createElement, createContext, useContext, useReducer } from 'react';
import { createContext, createElement, useContext, useReducer } from 'react';

const createReducerContext = <R extends React.Reducer<any, any>>(
reducer: R,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
const defaultMapPropsToArgs = (props) => [props];

const createRenderProp = (hook, mapPropsToArgs = defaultMapPropsToArgs) => {
const RenderProp = (props) => {
export default function createRenderProp(hook, mapPropsToArgs = defaultMapPropsToArgs) {
return function RenderProp(props) {
const state = hook(...mapPropsToArgs(props));
const { children, render = children } = props;
return render ? render(state) || null : null;
};

return RenderProp;
};

export default createRenderProp;
}
File renamed without changes.
Loading

0 comments on commit a27f09f

Please sign in to comment.