├── .browserslistrc ├── pnpm-workspace.yaml ├── scripts └── generate │ ├── templates │ ├── readme.tpl │ ├── index.tpl │ ├── test-dom.tpl │ ├── eslint.tpl │ ├── test-hook.tpl │ ├── tsconfig.tpl │ └── demo.tpl │ ├── template.js │ ├── dependencies.js │ └── prompt.js ├── packages ├── router │ ├── src │ │ ├── index.ts │ │ └── url.ts │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── docs │ │ ├── useSearchParamAll.md │ │ ├── useSearchParam.md │ │ ├── useNavigate.md │ │ ├── useSearchParamState.md │ │ ├── useUpdateSearchParams.md │ │ └── demo │ │ │ └── useSearchParamState.tsx │ └── README.md ├── boolean │ ├── .eslintrc.js │ ├── docs │ │ ├── demo │ │ │ ├── useBoolean.less │ │ │ └── useBoolean.tsx │ │ ├── useToggle.md │ │ ├── useSwitch.md │ │ └── useBoolean.md │ ├── tsconfig.json │ ├── README.md │ └── src │ │ └── index.ts ├── debounce │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ └── docs │ │ ├── useDebouncedValue.md │ │ ├── useDebouncedEffect.md │ │ ├── demo │ │ ├── useDebouncedValue.tsx │ │ └── useDebouncedEffect.tsx │ │ └── useDebouncedCallback.md ├── debug │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ └── docs │ │ ├── useRenderTimes.md │ │ ├── demo │ │ ├── useRenderTimes.tsx │ │ └── useChangeTimes.tsx │ │ └── useChangeTimes.md ├── hover │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ ├── docs │ │ ├── demo │ │ │ ├── useHover.tsx │ │ │ └── useHover2.tsx │ │ └── useHover.md │ └── package.json ├── immer │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ └── docs │ │ ├── useImmerState.md │ │ ├── useImmerReducer.md │ │ └── demo │ │ ├── useImmerState.tsx │ │ └── useImmerReducer.tsx ├── index │ ├── .eslintrc.js │ ├── tsconfig.json │ └── README.md ├── media │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ ├── docs │ │ ├── useMedia.md │ │ ├── demo │ │ │ ├── usePreferDarkMode.tsx │ │ │ └── useMedia.tsx │ │ └── usePreferDarkMode.md │ ├── src │ │ └── index.ts │ ├── demo │ │ └── entries │ │ │ └── index.js │ └── package.json ├── methods │ ├── .eslintrc.js │ ├── src │ │ ├── index.ts │ │ ├── interface.ts │ │ └── native.ts │ ├── tsconfig.json │ ├── docs │ │ ├── useMethodsExtensionNative.md │ │ ├── useMethodsNative.md │ │ └── useMethodsExtension.md │ └── README.md ├── network │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ ├── docs │ │ ├── useOnLine.md │ │ └── demo │ │ │ └── useOnLine.tsx │ └── src │ │ └── index.ts ├── number │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ ├── docs │ │ ├── demo │ │ │ └── useCounter.tsx │ │ └── useCounter.md │ └── src │ │ └── index.ts ├── poll │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ ├── docs │ │ └── demo │ │ │ ├── usePoll.tsx │ │ │ └── usePoll2.tsx │ └── demo │ │ └── entries │ │ └── index.js ├── request │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ └── docs │ │ ├── demo │ │ └── useRequest.tsx │ │ └── useRequestCallback.md ├── script │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ ├── demo │ │ ├── components │ │ │ ├── SimpleScript │ │ │ │ └── index.js │ │ │ └── WithSuspense │ │ │ │ └── index.js │ │ └── entries │ │ │ └── index.js │ ├── settings.js │ └── docs │ │ └── useScriptSuspense.md ├── snapshot │ ├── .eslintrc.js │ ├── src │ │ └── interface.ts │ ├── tsconfig.json │ ├── README.md │ ├── docs │ │ ├── demo │ │ │ └── useSnapshotState2.tsx │ │ └── useSnapshotState.md │ └── package.json ├── timeout │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ ├── docs │ │ ├── useStableInterval.md │ │ ├── useTimeout.md │ │ ├── useInterval.md │ │ └── demo │ │ │ ├── useInterval.tsx │ │ │ └── useTimeout.tsx │ └── package.json ├── update │ ├── .eslintrc.js │ ├── src │ │ ├── index.ts │ │ └── __tests__ │ │ │ └── index.test.js │ ├── tsconfig.json │ ├── README.md │ ├── docs │ │ ├── demo │ │ │ └── useForceUpdate.tsx │ │ └── useForceUpdate.md │ └── package.json ├── click-outside │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ ├── src │ │ ├── index.ts │ │ └── __tests__ │ │ │ └── index.test.js │ └── docs │ │ └── useClickOutside.md ├── collection │ ├── .eslintrc.js │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ ├── docs │ │ ├── demo │ │ │ ├── useArray.less │ │ │ └── useArray.tsx │ │ ├── useSet.md │ │ ├── useMap.md │ │ └── useArray.md │ └── README.md ├── derived-state │ ├── .eslintrc.js │ ├── docs │ │ └── demo │ │ │ └── useDerivedState.less │ ├── tsconfig.json │ ├── README.md │ ├── src │ │ ├── index.ts │ │ └── __tests__ │ │ │ └── index.test.js │ └── package.json ├── effect-ref │ ├── .eslintrc.js │ ├── docs │ │ ├── demo │ │ │ ├── useEffectRef.less │ │ │ └── useEffectRef.tsx │ │ └── useEffectRef.md │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── element-size │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ └── docs │ │ ├── demo │ │ ├── useElementSize.tsx │ │ └── useElementResize.tsx │ │ ├── useElementSize.md │ │ └── useElementResize.md ├── input-value │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ ├── src │ │ ├── index.ts │ │ └── __tests__ │ │ │ └── index.test.js │ ├── docs │ │ ├── useInputValue.md │ │ └── demo │ │ │ └── useInputValue.tsx │ └── package.json ├── intended-lazy │ ├── .eslintrc.js │ ├── README.md │ ├── tsconfig.json │ ├── src │ │ └── index.ts │ ├── docs │ │ ├── useIntendedLazyCallback.md │ │ └── useIntendedLazyValue.md │ ├── CHANGELOG.md │ └── package.json ├── intersection │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ ├── docs │ │ ├── demo │ │ │ ├── useOnScreen.less │ │ │ └── useOnScreen.tsx │ │ ├── useOnScreenCallback.md │ │ └── useOnScreen.md │ └── demo │ │ └── entries │ │ └── index.js ├── local-storage │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ └── docs │ │ ├── useLocalStorage.md │ │ └── demo │ │ └── useLocalStorage.tsx ├── merged-ref │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ ├── docs │ │ ├── useMergedRef.md │ │ └── demo │ │ │ └── useMergedRef.tsx │ └── package.json ├── optimistic │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ ├── src │ │ └── interface.ts │ ├── docs │ │ └── useOptimisticTask.md │ └── package.json ├── performance │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ ├── docs │ │ ├── useLayoutTiming.md │ │ ├── demo │ │ │ └── useLayoutTiming.tsx │ │ └── usePerformanceTiming.md │ └── package.json ├── scroll-lock │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ ├── docs │ │ ├── useScrollLock.md │ │ └── demo │ │ │ └── useScrollLock.tsx │ ├── src │ │ ├── index.ts │ │ └── __tests__ │ │ │ └── index.test.js │ └── package.json ├── user-media │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ └── docs │ │ └── useUserMedia.md ├── web-socket │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── src │ │ ├── constants.ts │ │ └── interface.ts │ └── README.md ├── window-size │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ ├── docs │ │ ├── demo │ │ │ └── useWindowSize.tsx │ │ └── useWindowSize.md │ └── src │ │ ├── __tests__ │ │ └── index.test.js │ │ └── index.ts ├── action-pending │ ├── .eslintrc.js │ ├── docs │ │ ├── demo │ │ │ ├── useActionPending.less │ │ │ └── useActionPending.tsx │ │ ├── useActionPending.zh-CN.md │ │ └── useActionPending.md │ ├── tsconfig.json │ ├── README.md │ └── src │ │ ├── __tests__ │ │ └── index.test.js │ │ └── index.ts ├── document-event │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ ├── docs │ │ ├── useDocumentEvent.md │ │ └── demo │ │ │ └── useDocumentEvent.tsx │ ├── src │ │ └── index.ts │ └── package.json ├── document-title │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ ├── src │ │ ├── index.ts │ │ └── __tests__ │ │ │ └── index.test.js │ ├── docs │ │ ├── useDocumentTitle.md │ │ └── demo │ │ │ └── useDocumentTitle.tsx │ └── package.json ├── infinite-scroll │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── demo │ │ └── entries │ │ │ └── index.less │ └── README.md ├── previous-value │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ └── docs │ │ ├── usePreviousEquals.md │ │ ├── useOriginalDeepCopy.md │ │ ├── demo │ │ ├── usePreviousValue.tsx │ │ └── useOriginalCopy.tsx │ │ └── usePreviousValue.md ├── scroll-into-view │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ ├── src │ │ ├── index.ts │ │ └── __tests__ │ │ │ └── index.test.js │ ├── demo │ │ └── entries │ │ │ └── index.less │ ├── docs │ │ └── useScrollIntoView.md │ └── package.json ├── scroll-position │ ├── .eslintrc.js │ ├── src │ │ └── has-passive-events.d.ts │ ├── tsconfig.json │ ├── docs │ │ ├── useScrollTop.md │ │ ├── useScrollLeft.md │ │ ├── demo │ │ │ └── useScrollPosition.tsx │ │ └── useScrollPosition.md │ ├── README.md │ └── package.json ├── transition-state │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── README.md │ ├── docs │ │ ├── useTransitionState.md │ │ └── demo │ │ │ └── useTransitionState.tsx │ ├── src │ │ └── index.ts │ └── package.json └── selection │ ├── tsconfig.json │ ├── docs │ ├── demo │ │ └── useSelection.less │ └── useSelection.md │ └── README.md ├── public └── style.css ├── lerna.json ├── docs ├── docs │ ├── packages.zh-CN.md │ └── packages.en-US.md ├── index.zh-CN.md └── index.en-US.md ├── tsconfig.json ├── .github └── workflows │ ├── ci.yml │ └── doc.yml ├── LICENSE └── .gitignore /.browserslistrc: -------------------------------------------------------------------------------- 1 | node >= 12 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | -------------------------------------------------------------------------------- /scripts/generate/templates/readme.tpl: -------------------------------------------------------------------------------- 1 | # @huse/%packageName% 2 | -------------------------------------------------------------------------------- /scripts/generate/templates/index.tpl: -------------------------------------------------------------------------------- 1 | export function %hookName%() { 2 | } 3 | -------------------------------------------------------------------------------- /packages/router/src/index.ts: -------------------------------------------------------------------------------- 1 | /* istanbul ignore file */ 2 | export * from './navigate'; 3 | export * from './search'; 4 | -------------------------------------------------------------------------------- /packages/boolean/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/debounce/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/debug/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/hover/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/immer/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/index/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/media/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/methods/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/network/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/number/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/poll/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/request/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/router/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/script/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/snapshot/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/timeout/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/update/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /scripts/generate/templates/test-dom.tpl: -------------------------------------------------------------------------------- 1 | import {render} from '@testing-library/react'; 2 | import {%hookName%} from '../index'; 3 | -------------------------------------------------------------------------------- /packages/click-outside/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/collection/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/derived-state/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/effect-ref/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/element-size/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/input-value/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/intended-lazy/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/intersection/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/local-storage/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/merged-ref/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/optimistic/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/performance/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/scroll-lock/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/user-media/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/web-socket/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/window-size/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/action-pending/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/document-event/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/document-title/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/infinite-scroll/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/previous-value/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/scroll-into-view/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/scroll-position/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/transition-state/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /scripts/generate/templates/eslint.tpl: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@reskript/config-lint/config/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /scripts/generate/templates/test-hook.tpl: -------------------------------------------------------------------------------- 1 | import {renderHook} from '@testing-library/react-hooks'; 2 | import {%hookName%} from '../index'; 3 | -------------------------------------------------------------------------------- /packages/methods/src/index.ts: -------------------------------------------------------------------------------- 1 | /* istanbul ignore file */ 2 | export * from './interface'; 3 | export * from './native'; 4 | export * from './immer'; 5 | -------------------------------------------------------------------------------- /packages/snapshot/src/interface.ts: -------------------------------------------------------------------------------- 1 | export interface SnapshotState { 2 | pendingValue: T; 3 | history: T[]; 4 | version: number | null; 5 | } 6 | -------------------------------------------------------------------------------- /packages/update/src/index.ts: -------------------------------------------------------------------------------- 1 | import {useReducer} from 'react'; 2 | 3 | export function useForceUpdate() { 4 | return useReducer((v: number) => v + 1, 0)[1]; 5 | } 6 | -------------------------------------------------------------------------------- /packages/effect-ref/docs/demo/useEffectRef.less: -------------------------------------------------------------------------------- 1 | .effect-select::before { 2 | content: 'choose a tag:'; 3 | display: inline-block; 4 | margin-right: 10px; 5 | } 6 | -------------------------------------------------------------------------------- /packages/intended-lazy/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Hooks 4 | path: /hook 5 | group: 6 | title: Intended Lazy 7 | path: /intended-lazy 8 | hide: true 9 | --- 10 | -------------------------------------------------------------------------------- /packages/scroll-position/src/has-passive-events.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | declare module 'has-passive-events' { 3 | const hasPassive: boolean; 4 | export default hasPassive; 5 | } 6 | -------------------------------------------------------------------------------- /packages/action-pending/docs/demo/useActionPending.less: -------------------------------------------------------------------------------- 1 | .btns { 2 | margin-bottom: 30px; 3 | 4 | button { 5 | margin-right: 20px; 6 | margin-bottom: 10px; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/collection/src/index.ts: -------------------------------------------------------------------------------- 1 | /* istanbul ignore file */ 2 | export {default as useArray} from './array'; 3 | export {default as useSet} from './set'; 4 | export {default as useMap} from './map'; 5 | -------------------------------------------------------------------------------- /packages/boolean/docs/demo/useBoolean.less: -------------------------------------------------------------------------------- 1 | .button1, 2 | .button2, 3 | .button3 { 4 | margin-right: 20px; 5 | margin-bottom: 10px; 6 | } 7 | 8 | .switch { 9 | margin-top: 20px; 10 | } 11 | -------------------------------------------------------------------------------- /packages/boolean/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/debug/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/derived-state/docs/demo/useDerivedState.less: -------------------------------------------------------------------------------- 1 | .memo-box { 2 | margin-top: 30px; 3 | } 4 | 5 | .memo-tips { 6 | font-size: 16px; 7 | margin-top: 10px; 8 | color: red; 9 | } 10 | -------------------------------------------------------------------------------- /packages/hover/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/immer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/index/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/media/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/methods/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/network/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/number/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/poll/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/request/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/script/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/timeout/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/update/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/collection/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/debounce/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/effect-ref/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/element-size/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/input-value/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/intersection/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/merged-ref/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/optimistic/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/performance/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/scroll-lock/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/selection/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/snapshot/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/user-media/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/web-socket/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/window-size/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/action-pending/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/click-outside/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/derived-state/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/document-event/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/document-title/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/infinite-scroll/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/intended-lazy/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/local-storage/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/previous-value/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/scroll-into-view/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/scroll-position/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/transition-state/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /scripts/generate/templates/tsconfig.tpl: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/boolean/docs/useToggle.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useToggle 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Boolean 8 | path: /boolean 9 | order: 4 10 | --- 11 | 12 | # useToggle 13 | 14 | `useToggle` returns a `[value, toggle]` tuple. -------------------------------------------------------------------------------- /packages/router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./cjs", 6 | "lib": ["ES2016", "DOM", "DOM.Iterable"] 7 | }, 8 | "include": ["src"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/selection/docs/demo/useSelection.less: -------------------------------------------------------------------------------- 1 | .text-style { 2 | height: 30px; 3 | line-height: 30px; 4 | padding: 0 10px; 5 | border-bottom: 1px solid #ccc; 6 | cursor: default; 7 | user-select: none; 8 | font-family: Hack, monospace; 9 | } 10 | -------------------------------------------------------------------------------- /packages/infinite-scroll/demo/entries/index.less: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | } 5 | 6 | .item { 7 | height: 20vh; 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | font-size: 24px; 12 | color: #fff; 13 | } 14 | -------------------------------------------------------------------------------- /packages/collection/docs/demo/useArray.less: -------------------------------------------------------------------------------- 1 | .click-btn { 2 | margin-bottom: 30px; 3 | } 4 | 5 | .item-style { 6 | height: 30px; 7 | display: flex; 8 | align-items: center; 9 | justify-content: center; 10 | font-size: 20px; 11 | color: #fff; 12 | } 13 | -------------------------------------------------------------------------------- /packages/update/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Update 8 | path: /update 9 | order: 1 10 | --- 11 | 12 | # Update 13 | 14 | Force component to update. 15 | 16 | ```shell 17 | npm install @huse/update 18 | ``` -------------------------------------------------------------------------------- /packages/script/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Script 8 | path: /script 9 | order: 1 10 | --- 11 | 12 | # Script 13 | 14 | Dynamically load a script via hook. 15 | 16 | ```shell 17 | npm install @huse/script 18 | ``` -------------------------------------------------------------------------------- /packages/scroll-into-view/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Scroll Into View 8 | path: /scroll-into-view 9 | order: 1 10 | --- 11 | 12 | # Scroll Into View 13 | 14 | ```shell 15 | npm install @huse/scroll-into-view 16 | ``` -------------------------------------------------------------------------------- /packages/timeout/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Timeout 8 | path: /timeout 9 | order: 1 10 | --- 11 | 12 | # Timeout 13 | 14 | Hooks about timeout and interval. 15 | 16 | ```shell 17 | npm install @huse/timeout 18 | ``` -------------------------------------------------------------------------------- /packages/media/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Media 8 | path: /media 9 | order: 1 10 | --- 11 | 12 | # Media 13 | 14 | Provide hooks to observe media query matches. 15 | 16 | ```shell 17 | npm install @huse/media 18 | ``` -------------------------------------------------------------------------------- /packages/number/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Number 8 | path: /number 9 | order: 1 10 | --- 11 | 12 | # Number 13 | 14 | Hooks to manage and mutate number types. 15 | 16 | ```shell 17 | npm install @huse/number 18 | ``` -------------------------------------------------------------------------------- /packages/web-socket/src/constants.ts: -------------------------------------------------------------------------------- 1 | export enum ReadyState { 2 | UNINSTANTIATED = -1, 3 | CONNECTING = 0, 4 | OPEN = 1, 5 | CLOSING = 2, 6 | CLOSED = 3, 7 | } 8 | 9 | export const DEFAULT_RECONNECT_LIMIT = 10; 10 | 11 | export const DEFAULT_RECONNECT_INTERVAL_MS = 5000; 12 | -------------------------------------------------------------------------------- /scripts/generate/templates/demo.tpl: -------------------------------------------------------------------------------- 1 | import {render} from 'react-dom'; 2 | import {%hookName%} from '../../src'; 3 | 4 | const App = () => { 5 | return
Hello World
; 6 | }; 7 | 8 | render( 9 | , 10 | document.body.appendChild(document.createElement('div')) 11 | ); 12 | -------------------------------------------------------------------------------- /packages/poll/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Poll 8 | path: /poll 9 | order: 1 10 | --- 11 | 12 | # Poll 13 | 14 | Poll data periodically and update corresponding state. 15 | 16 | ```shell 17 | npm install @huse/poll 18 | ``` -------------------------------------------------------------------------------- /packages/web-socket/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Web Socket 8 | path: /web-socket 9 | order: 1 10 | --- 11 | 12 | # Web Socket 13 | Hooks to work with web socket. 14 | 15 | ```shell 16 | npm install @huse/web-socket 17 | ``` -------------------------------------------------------------------------------- /packages/merged-ref/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Merged Ref 8 | path: /merged-ref 9 | order: 1 10 | --- 11 | 12 | # Merged Ref 13 | 14 | Merge multiple refs into one. 15 | 16 | ```shell 17 | npm install @huse/merged-ref 18 | ``` -------------------------------------------------------------------------------- /packages/boolean/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Boolean 8 | path: /boolean 9 | order: 1 10 | --- 11 | 12 | # Boolean 13 | 14 | Provides hooks to work with `boolean` primitive type. 15 | 16 | ```shell 17 | npm install @huse/boolean 18 | ``` -------------------------------------------------------------------------------- /packages/hover/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Hover 8 | path: /hover 9 | order: 1 10 | --- 11 | 12 | # Hover 13 | 14 | Provides a hook to observe whether element is in hover state. 15 | 16 | ```shell 17 | npm install @huse/hover 18 | ``` -------------------------------------------------------------------------------- /packages/derived-state/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Derived State 8 | path: /derived-state 9 | order: 1 10 | --- 11 | 12 | # Derived State 13 | 14 | Derive a value from input. 15 | 16 | ```shell 17 | npm install @huse/derived-state 18 | ``` -------------------------------------------------------------------------------- /packages/scroll-lock/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Scroll Lock 8 | path: /scroll-lock 9 | order: 1 10 | --- 11 | 12 | # Scroll Lock 13 | 14 | Lock and unlock scroll conditionally. 15 | 16 | ```shell 17 | npm install @huse/scroll-lock 18 | ``` -------------------------------------------------------------------------------- /packages/window-size/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Window Size 8 | path: /window-size 9 | order: 1 10 | --- 11 | 12 | # Window Size 13 | 14 | Read and observe the size of viewport. 15 | 16 | ```shell 17 | npm install @huse/window-size 18 | ``` -------------------------------------------------------------------------------- /packages/collection/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Collection 8 | path: /collection 9 | order: 1 10 | --- 11 | 12 | # Collection 13 | 14 | Hooks to manage and mutate collection types. 15 | 16 | ```shell 17 | npm install @huse/collection 18 | ``` -------------------------------------------------------------------------------- /packages/methods/docs/useMethodsExtensionNative.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useMethodsExtensionNative 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Methods 8 | path: /methods 9 | order: 5 10 | --- 11 | 12 | # useMethodsExtensionNative 13 | 14 | Like `useMethodsExtension` but for native `useState` function. -------------------------------------------------------------------------------- /packages/scroll-position/docs/useScrollTop.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useScrollTop 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Scroll Position 8 | path: /scroll-position 9 | order: 4 10 | --- 11 | 12 | # useScrollTop 13 | 14 | `useScrollTop` is a short hook to get the `top` value of `useScrollPosition`. -------------------------------------------------------------------------------- /packages/debounce/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Debounce 8 | path: /debounce 9 | order: 1 10 | --- 11 | 12 | # Debounce 13 | 14 | Provides hooks to debounce value changes, effects or callbacks. 15 | 16 | ```shell 17 | npm install @huse/debounce 18 | ``` -------------------------------------------------------------------------------- /packages/element-size/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Element Size 8 | path: /element-size 9 | order: 1 10 | --- 11 | 12 | # Element Size 13 | 14 | Hooks related to observing element size. 15 | 16 | ```shell 17 | npm install @huse/element-size 18 | ``` -------------------------------------------------------------------------------- /packages/scroll-position/docs/useScrollLeft.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useScrollLeft 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Scroll Position 8 | path: /scroll-position 9 | order: 3 10 | --- 11 | 12 | # useScrollLeft 13 | 14 | `useScrollLeft` is a short hook to get the `left` value of `useScrollPosition`. -------------------------------------------------------------------------------- /packages/selection/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Selection 8 | path: /selection 9 | order: 1 10 | --- 11 | 12 | # Selection 13 | 14 | Hooks about selecting on a list of items via mouse clicks. 15 | 16 | ```shell 17 | npm install @huse/selection 18 | ``` -------------------------------------------------------------------------------- /packages/local-storage/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Local Storage 8 | path: /local-storage 9 | order: 1 10 | --- 11 | 12 | # Local Storage 13 | 14 | Access, observe and update `localStorage`. 15 | 16 | ```shell 17 | npm install @huse/local-storage 18 | ``` -------------------------------------------------------------------------------- /packages/document-event/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Document Event 8 | path: /document-event 9 | order: 1 10 | --- 11 | 12 | # Document Event 13 | 14 | Register event listeners on `document`. 15 | 16 | ```shell 17 | npm install @huse/document-event 18 | ``` -------------------------------------------------------------------------------- /packages/document-title/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Document Title 8 | path: /document-title 9 | order: 1 10 | --- 11 | 12 | # Document Title 13 | 14 | Change `document.title` to given value. 15 | 16 | ```shell 17 | npm install @huse/document-title 18 | ``` -------------------------------------------------------------------------------- /packages/performance/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Performance 8 | path: /performance 9 | order: 1 10 | --- 11 | 12 | # Performance 13 | 14 | Provides hooks to track and report component performance. 15 | 16 | ```shell 17 | npm install @huse/performance 18 | ``` -------------------------------------------------------------------------------- /packages/input-value/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Input Value 8 | path: /input-value 9 | order: 1 10 | --- 11 | 12 | # Input Value 13 | 14 | Generates `value` and `onChange` that satisfies input elements. 15 | 16 | ```shell 17 | npm install @huse/input-value 18 | ``` -------------------------------------------------------------------------------- /packages/methods/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Methods 8 | path: /methods 9 | order: 1 10 | --- 11 | 12 | # Methods 13 | 14 | Provides infrastructure hooks to encapsulate a state and some methods together. 15 | 16 | ```shell 17 | npm install @huse/methods 18 | ``` -------------------------------------------------------------------------------- /packages/network/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Network 8 | path: /network 9 | order: 1 10 | --- 11 | 12 | # Network 13 | 14 | Provides hooks to resolve and listen on network states like online or connectivity. 15 | 16 | ```shell 17 | npm install @huse/network 18 | ``` -------------------------------------------------------------------------------- /packages/snapshot/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Snapshot 8 | path: /snapshot 9 | order: 1 10 | --- 11 | 12 | # Snapshot 13 | 14 | Provides hook to manage value history and work with undo and redo features. 15 | 16 | ```shell 17 | npm install @huse/snapshot 18 | ``` -------------------------------------------------------------------------------- /packages/user-media/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: User Media 8 | path: /user-media 9 | order: 1 10 | --- 11 | 12 | # User Media 13 | 14 | Open a media stream in browser to produce video and audio from client. 15 | 16 | ```shell 17 | npm install @huse/user-media 18 | ``` -------------------------------------------------------------------------------- /packages/action-pending/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Action Pending 8 | path: /action-pending 9 | order: 1 10 | --- 11 | 12 | # Action Pending 13 | 14 | Hooks to encapsulate async function with pending states. 15 | 16 | ```shell 17 | npm install @huse/action-pending 18 | ``` -------------------------------------------------------------------------------- /packages/boolean/docs/useSwitch.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useSwitch 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Boolean 8 | path: /boolean 9 | order: 3 10 | --- 11 | 12 | # useSwitch 13 | 14 | `useSwitch` returns a `[value, on, off, toggle]` tuple, this is simple a wrapper hook around `useBoolean` and expand all methods into tuple. -------------------------------------------------------------------------------- /packages/infinite-scroll/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Infinite Scroll 8 | path: /infinite-scroll 9 | order: 1 10 | --- 11 | 12 | # Infinite Scroll 13 | 14 | Provides hooks to work with scroll-to-load cases. 15 | 16 | ```shell 17 | npm install @huse/infinite-scroll 18 | ``` -------------------------------------------------------------------------------- /packages/intersection/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Intersection 8 | path: /intersection 9 | order: 1 10 | --- 11 | 12 | # Intersection 13 | 14 | Provides hooks to observe intersection between element and viewport. 15 | 16 | ```shell 17 | npm install @huse/intersection 18 | ``` -------------------------------------------------------------------------------- /packages/intersection/docs/demo/useOnScreen.less: -------------------------------------------------------------------------------- 1 | .tips, 2 | .change-tips { 3 | color: #000; 4 | font-size: 20px; 5 | } 6 | 7 | .change-tips { 8 | color: red; 9 | margin-left: 5px; 10 | } 11 | 12 | .visual-port { 13 | margin-top: 600px; 14 | padding: 40px; 15 | background: #007bd2; 16 | font-size: 30px; 17 | color: #fff; 18 | } -------------------------------------------------------------------------------- /packages/scroll-position/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Scroll Position 8 | path: /scroll-position 9 | order: 1 10 | --- 11 | 12 | # Scroll Position 13 | 14 | Provides a set of hooks to observe element or window scroll position. 15 | 16 | ```shell 17 | npm install @huse/scroll-position 18 | ``` -------------------------------------------------------------------------------- /packages/click-outside/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Click Outside 8 | path: /click-outside 9 | order: 1 10 | --- 11 | 12 | # Click Outside 13 | 14 | Listen for clicks in document and trigger a callback when it happens outside an element. 15 | 16 | ```shell 17 | npm install @huse/click-outside 18 | ``` -------------------------------------------------------------------------------- /packages/router/docs/useSearchParamAll.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useSearchParamAll 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Router 8 | path: /router 9 | order: 6 10 | --- 11 | 12 | # useSearchParamAll 13 | 14 | Like `useSearchParams` but get a single param as array. 15 | 16 | ```typescript 17 | function useSearchParamAll(key: string): string[] 18 | ``` -------------------------------------------------------------------------------- /packages/previous-value/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Previous Value 8 | path: /previous-value 9 | order: 1 10 | --- 11 | 12 | # Previous Value 13 | 14 | Provides hooks about retrieving and comparing current value with the previous version. 15 | 16 | ```shell 17 | npm install @huse/previous-value 18 | ``` -------------------------------------------------------------------------------- /packages/router/docs/useSearchParam.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useSearchParam 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Router 8 | path: /router 9 | order: 5 10 | --- 11 | 12 | # useSearchParam 13 | 14 | Like `useSearchParams` but get a single param without update function. 15 | 16 | ```typescript 17 | function useSearchParam(key: string): string | null 18 | ``` -------------------------------------------------------------------------------- /packages/effect-ref/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Effect Ref 8 | path: /effect-ref 9 | order: 1 10 | --- 11 | 12 | # Effect Ref 13 | 14 | Makes a [callback ref](https://zh-hans.reactjs.org/docs/refs-and-the-dom.html#callback-refs) behaves like effects. 15 | 16 | ```shell 17 | npm install @huse/effect-ref 18 | ``` -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | .__dumi-default-navbar-logo { 2 | color: transparent !important; /*干掉title 只显示logo*/ 3 | } 4 | .__dumi-default-navbar > nav span { 5 | display: none !important; /* 干掉没用的bar */ 6 | } 7 | 8 | .__dumi-default-navbar > nav span:nth-child(3), 9 | .__dumi-default-navbar > nav span:last-of-type { 10 | display:inline-block !important;/*这里有个坑 bar上加了几个你就得手动显示几个*/ 11 | } 12 | -------------------------------------------------------------------------------- /packages/transition-state/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Transition State 8 | path: /transition-state 9 | order: 1 10 | --- 11 | 12 | # Transition State 13 | 14 | Provide a hook which will go back to its default value after a certain duration when set to a new value. 15 | 16 | ```shell 17 | npm install @huse/transition-state 18 | ``` -------------------------------------------------------------------------------- /packages/document-title/src/index.ts: -------------------------------------------------------------------------------- 1 | import {useLayoutEffect} from 'react'; 2 | 3 | export function useDocumentTitle(title: string) { 4 | useLayoutEffect( 5 | () => { 6 | const previous = document.title; 7 | document.title = title; 8 | return () => { 9 | document.title = previous; 10 | }; 11 | }, 12 | [title] 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /packages/request/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Request 8 | path: /request 9 | order: 1 10 | --- 11 | 12 | # Request 13 | 14 | Tries to provide basic capabilities to make a request inside react components. 15 | 16 | By "request" we mean any asynchronous mapping from a input to an output, typically a HTTP fetch. 17 | 18 | ```shell 19 | npm install @huse/request 20 | ``` -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "independent", 6 | "npmClient": "yarn", 7 | "useWorkspaces": true, 8 | "command": { 9 | "version": { 10 | "allowBranch": "master", 11 | "ignoreChanges": [ 12 | "**/*.md", 13 | "**/example/**", 14 | "**/__tests__/**", 15 | "**/.eslintrc.js", 16 | "**/tsconfig.json" 17 | ] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/network/docs/useOnLine.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useOnLine 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Network 8 | path: /network 9 | order: 2 10 | --- 11 | 12 | # useOnLine 13 | 14 | `useOnLine` returns a boolean indicates whether client is currently online. 15 | 16 | In case where client doesn't support `online` and `offline` events, this hook always returns `true`. 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/debug/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Debug 8 | path: /debug 9 | order: 1 10 | --- 11 | 12 | # Debug 13 | 14 | A set of hooks for debugging component. 15 | 16 | Since this package is for debugging, we will not consider the size of package as an important factor, please remember to remove all debug hooks in production. 17 | 18 | ```shell 19 | npm install @huse/debug 20 | ``` -------------------------------------------------------------------------------- /scripts/generate/template.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs-extra'); 3 | const replace = (input, vars) => input.replace(/%(\w+)%/g, (match, name) => vars[name]); 4 | 5 | module.exports = async (name, destination, vars) => { 6 | await fs.ensureFile(destination); 7 | const template = fs.readFileSync(path.join(__dirname, 'templates', `${name}.tpl`), 'utf-8'); 8 | await fs.writeFile(destination, replace(template, vars)); 9 | }; 10 | -------------------------------------------------------------------------------- /packages/immer/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Immer 8 | path: /immer 9 | order: 1 10 | --- 11 | 12 | # Immer 13 | 14 | **DEPRECATED: This package is depreacted in favor of [the offficial use-immer package](https://github.com/immerjs/use-immer).** 15 | 16 | Provides reducer and state hooks bound to [immer](https://github.com/immerjs/immer) library. 17 | 18 | ```shell 19 | npm install @huse/immer 20 | ``` -------------------------------------------------------------------------------- /packages/methods/src/interface.ts: -------------------------------------------------------------------------------- 1 | /* istanbul ignore file */ 2 | export interface NativeReducers { 3 | [key: string]: (state: S, ...args: any[]) => S; 4 | } 5 | 6 | export interface ImmerReducers { 7 | [key: string]: (state: S, ...args: any[]) => S | void; 8 | } 9 | 10 | type Strip = T extends (state: any, ...args: infer P) => any ? (...args: P) => void : never; 11 | 12 | export type Methods | ImmerReducers> = {[K in keyof R]: Strip}; 13 | -------------------------------------------------------------------------------- /packages/router/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Router 8 | path: /router 9 | order: 1 10 | --- 11 | 12 | # Router 13 | 14 | Enpower `react-router-dom` with hooks to interactive with location, state, search params and more. 15 | 16 | **NOTE: This packages works only with `v5.x` and `react-router-dom` currently, `react-router-native` and `6.x` version are not supported.** 17 | 18 | ```shell 19 | npm install @huse/router 20 | ``` -------------------------------------------------------------------------------- /packages/media/docs/useMedia.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useMedia 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Media 8 | path: /media 9 | order: 2 10 | --- 11 | 12 | # useMedia 13 | 14 | Observe the given media query and return whether it is satisfied by current environment. 15 | 16 | ```typescript 17 | function useMedia(query: string): boolean; 18 | ``` 19 | 20 | The return value is responsive to viewport and device changes. 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /packages/timeout/docs/useStableInterval.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useStableInterval 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Timeout 8 | path: /timeout 9 | order: 4 10 | --- 11 | 12 | # useStableInterval 13 | 14 | Like `useInterval` but counts time ellapsed to execute `callback`, when `callback` returns a `Promise`, it's resolve time is counted. 15 | 16 | `useStableInterval` ensures the next execution of callback is triggered after specified `delay` after `callback` is returned or resolved. -------------------------------------------------------------------------------- /packages/debug/docs/useRenderTimes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useRenderTimes 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Debug 8 | path: /debug 9 | order: 2 10 | --- 11 | 12 | # useRenderTimes 13 | 14 | Returns the times of render triggered. 15 | 16 | ```typescript 17 | function useRenderTimes(): number 18 | ``` 19 | 20 | This returned times starts from `1` and increase on each render, even props and states are not changed. 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/window-size/docs/demo/useWindowSize.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useWindowSize} from '@huse/window-size'; 3 | 4 | export default () => { 5 | const size = useWindowSize(); 6 | return ( 7 | <> 8 |

Outer: {size.outerWidth} x {size.outerHeight}

9 |

Inner: {size.innerWidth} x {size.innerHeight}

10 |

Resize window to update

11 | 12 | ); 13 | }; -------------------------------------------------------------------------------- /docs/docs/packages.zh-CN.md: -------------------------------------------------------------------------------- 1 | # 项目结构 2 | 3 | 项目基于`yarn`来构建基本的工作环境,每一个hook都在`packages`目录文件夹下,使用`xx-xx`破折号分割, 没有使用`use`前缀, 4 | 5 | 6 | 包名要确保是`@huse/foo-bar`这种格式的 7 | 8 | ```bash 9 | 10 | packages 11 | │ ├── action-pending 12 | │ ├── window-size 13 | │ ├── foo-bar 14 | │ └── xxx-xxx 15 | 16 | ``` 17 | 18 | 要注意每一个包的导出和命名规范 , 使用时如下示例: 19 | 20 | ```js 21 | import { useInputValue } from '@huse/input-value'; 22 | ``` 23 | 24 | ## 单元测试 25 | 我们推荐你使用单元测试,可以把单元测试文件放在`src/__tests__`文件夹下,以`.test.js`来结尾命名,我们强烈建议你的单测覆盖率要达到100%。 -------------------------------------------------------------------------------- /packages/methods/docs/useMethodsNative.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useMethodsNative 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Methods 8 | path: /methods 9 | order: 4 10 | --- 11 | 12 | # useMethodsNative 13 | 14 | Like `useMethods` but works without immer support, this is used to wrap state of type which immer cannot handle, such like `Map`, `Set` and custom classes. 15 | 16 | As a result of the absence of immer, reducer does not allow in-place mutation to states, it must return a new state object. 17 | -------------------------------------------------------------------------------- /packages/media/docs/demo/usePreferDarkMode.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useMedia, usePreferDarkMode} from '@huse/media'; 3 | 4 | export default () => { 5 | const darkMode = usePreferDarkMode(); 6 | const backgroundColor = darkMode ? '#222' : '#fff'; 7 | const color = darkMode ? '#f5f6f7' : '#666'; 8 | return ( 9 |
10 | Good {darkMode ? 'Evening' : 'Morning'} 11 |
12 | ); 13 | }; -------------------------------------------------------------------------------- /packages/timeout/docs/useTimeout.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useTimeout 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Timeout 8 | path: /timeout 9 | order: 2 10 | --- 11 | 12 | # useTimeout 13 | 14 | Set a timeout to execute callback. 15 | 16 | ```typescript 17 | function useTimeout(callback: (() => void) | undefined, time: number): void; 18 | ``` 19 | 20 | `callback` is not required to be reference equal. 21 | 22 | To cancel timeout, pass a negative `time`, `-1` is recommended. 23 | 24 | 25 | -------------------------------------------------------------------------------- /packages/router/docs/useNavigate.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useNavigate 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Router 8 | path: /router 9 | order: 2 10 | --- 11 | 12 | # useNavigate 13 | 14 | A implement of `react-router@6.x`'s `useNavigate` above version `5.x`. 15 | 16 | ```typescript 17 | interface NavigateOptions { 18 | replace?: boolean; 19 | state?: S; 20 | } 21 | 22 | type Navigate = (to: Location | string, options?: NavigateOptions) => void; 23 | 24 | function useNavigate(): Navigate; 25 | ``` -------------------------------------------------------------------------------- /packages/click-outside/src/index.ts: -------------------------------------------------------------------------------- 1 | import {RefObject} from 'react'; 2 | import {useDocumentEvent} from '@huse/document-event'; 3 | 4 | export function useClickOutside(ref: RefObject, callback: (e: MouseEvent | TouchEvent) => void) { 5 | const testAndTrigger = (e: MouseEvent | TouchEvent) => { 6 | if (!ref.current?.contains(e.target as Element)) { 7 | callback(e); 8 | } 9 | }; 10 | 11 | useDocumentEvent('mouseup', testAndTrigger, true); 12 | useDocumentEvent('touchstart', testAndTrigger, true); 13 | } 14 | -------------------------------------------------------------------------------- /packages/element-size/docs/demo/useElementSize.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState, useReducer, useCallback} from 'react'; 2 | import {useElementResize, useElementSize} from '@huse/element-size'; 3 | 4 | export default () => { 5 | const [ref, size] = useElementSize(); 6 | return ( 7 |
11 | {size && `${size.width} x ${size.height}`} 12 |
13 | ); 14 | }; -------------------------------------------------------------------------------- /packages/hover/docs/demo/useHover.tsx: -------------------------------------------------------------------------------- 1 | import React, {useReducer, useCallback} from 'react'; 2 | import {useHover} from '@huse/hover'; 3 | 4 | export default () => { 5 | const [hoverTimes, enter] = useReducer(v => v + 1, 0); 6 | const [isHover, hoverCallbacks] = useHover({onEnter: enter}); 7 | return ( 8 |
9 | Hovered 10 | {hoverTimes} times 11 | {isHover && '(hovered)'} 12 |
13 | ); 14 | }; -------------------------------------------------------------------------------- /packages/media/docs/usePreferDarkMode.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: usePreferDarkMode 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Media 8 | path: /media 9 | order: 3 10 | --- 11 | 12 | # usePreferDarkMode 13 | 14 | A shortcut hook to determine whether current user prefers dark mode. 15 | 16 | ```typescript 17 | function usePreferDarkMode(): boolean; 18 | ``` 19 | 20 | This hook is also responsive, that means the value will be updated if system changes color scheme or having a automatic scheme. 21 | 22 | -------------------------------------------------------------------------------- /packages/debounce/docs/useDebouncedValue.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useDebouncedValue 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Debounce 8 | path: /debounce 9 | order: 3 10 | --- 11 | 12 | # useDebouncedValue 13 | 14 | Derive a given value and debounce its update by a given delay. 15 | 16 | ```typescript 17 | function useDebouncedValue(value: T, wait: number): T 18 | ``` 19 | 20 | Returned value will not update unless the input value stops change longer than `wait`. 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/input-value/src/index.ts: -------------------------------------------------------------------------------- 1 | import {useState, useCallback} from 'react'; 2 | 3 | interface ChangeEvent { 4 | target: {value: string}; 5 | } 6 | 7 | export interface InputValueState { 8 | value: string; 9 | onChange(e: ChangeEvent): void; 10 | } 11 | 12 | export function useInputValue(initialValue: string = ''): InputValueState { 13 | const [value, setValue] = useState(initialValue); 14 | const onChange = useCallback( 15 | (e: ChangeEvent) => setValue(e.target.value), 16 | [] 17 | ); 18 | return {value, onChange}; 19 | } 20 | -------------------------------------------------------------------------------- /packages/router/docs/useSearchParamState.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useSearchParamState 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Router 8 | path: /router 9 | order: 8 10 | --- 11 | 12 | # useSearchParamState 13 | 14 | Wrap a single search params as a react state. 15 | 16 | ```typescript 17 | function useSearchParamState(key: string, options?: NavigateOptions): [string | null, (value: string) => void]; 18 | ``` 19 | 20 | When a state is stored in search params, using this hook works just like `useState`. 21 | 22 | -------------------------------------------------------------------------------- /packages/update/src/__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | import {renderHook, act} from '@testing-library/react-hooks'; 2 | import {useForceUpdate} from '../index'; 3 | 4 | test('valid return type', () => { 5 | const {result} = renderHook(() => useForceUpdate()); 6 | expect(typeof result.current).toBe('function'); 7 | }); 8 | 9 | test('will force update', () => { 10 | const init = jest.fn(() => useForceUpdate()); 11 | const {result} = renderHook(init); 12 | expect(init).toHaveBeenCalledTimes(1); 13 | act(() => result.current()); 14 | expect(init).toHaveBeenCalledTimes(2); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/update/docs/demo/useForceUpdate.tsx: -------------------------------------------------------------------------------- 1 | import React, {useRef} from 'react'; 2 | import {Button} from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import {useForceUpdate} from '@huse/update'; 5 | 6 | export default () => { 7 | const renderTimes = useRef(0); 8 | const forceUpdate = useForceUpdate(); 9 | renderTimes.current++; 10 | return ( 11 |
12 |

Rendered {renderTimes.current} times

13 | 14 |
15 | ); 16 | }; -------------------------------------------------------------------------------- /packages/previous-value/docs/usePreviousEquals.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: usePreviousEquals 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Previous Value 8 | path: /previous-value 9 | order: 5 10 | --- 11 | 12 | # usePreviousEquals 13 | 14 | Determine whether value is changed in current render. 15 | 16 | ```typescript 17 | type CustomEquals = (previous: T | undefined, current: T) => boolean 18 | function usePreviousEquals(value: T, equals?: CustomEquals): boolean; 19 | ``` 20 | 21 | By default a shallow compare is performed to check equality, a custom equality function can be provided. -------------------------------------------------------------------------------- /packages/script/demo/components/SimpleScript/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-no-bind */ 2 | import {useState} from 'react'; 3 | import {useScript} from '../../../src'; 4 | 5 | const SimpleScript = () => { 6 | const [index, setIndex] = useState(0); 7 | useScript(index === 0 ? undefined : `/${index}.js`); 8 | 9 | return ( 10 |
11 | 12 | 13 |
14 | ); 15 | }; 16 | 17 | export default SimpleScript; 18 | -------------------------------------------------------------------------------- /packages/window-size/docs/useWindowSize.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useWindowSize 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Window Size 8 | path: /window-size 9 | order: 2 10 | --- 11 | 12 | # useWindowSize 13 | 14 | Retrieve current window size, both inner and outer. 15 | 16 | ```typescript 17 | interface WindowSize { 18 | innerWidth: number; 19 | innerHeight: number; 20 | outerWidth: number; 21 | outerHeight: number; 22 | } 23 | 24 | function useWindowSize(): WindowSize; 25 | ``` 26 | 27 | State will update when window resizes. 28 | 29 | 30 | -------------------------------------------------------------------------------- /packages/previous-value/docs/useOriginalDeepCopy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useOriginalDeepCopy 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Previous Value 8 | path: /previous-value 9 | order: 4 10 | --- 11 | 12 | # useOriginalDeepCopy 13 | 14 | This is a short cut version of `useOriginalCopy` using a deep equality function. 15 | 16 | ```typescript 17 | type CustomEquals = (previous: T | undefined, current: T) => boolean; 18 | function useOriginalDeepCopy(value: T): T; 19 | ``` 20 | 21 | In short, this is exactly the same as: 22 | 23 | ```javascript 24 | return useOriginalCopy(value, deepEquals) 25 | ``` -------------------------------------------------------------------------------- /packages/action-pending/docs/useActionPending.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useActionPending 3 | nav: 4 | title: Hooks 5 | path: /zh-CN/hook 6 | group: 7 | title: Action Pending 8 | path: /action-pending 9 | order: 2 10 | --- 11 | 12 | # useActionPending 13 | 14 | 当你传递一个异步函数给这个hook时,它会返回一个封装后的函数和一个“异步进行中”数量。 15 | 16 | ```typescript 17 | type AsyncFunction = (...args: any[]) => Promise; 18 | 19 | function useActionPending(action: A): [A, number] 20 | ``` 21 | 22 | 在返回的元组中,第二项称为`pendingCount`,你可以简单地用`!!pendingCount`来判断是否还有未完成的异步,并在界面上表现为一个“加载中”的视图。 23 | 24 | -------------------------------------------------------------------------------- /packages/document-title/docs/useDocumentTitle.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useDocumentTitle 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Document Title 8 | path: /document-title 9 | order: 1 10 | --- 11 | 12 | # useDocumentTitle 13 | 14 | This hook updates `document.title` to custom value and will revert the change on component unmount. 15 | 16 | ```typescript 17 | function useDocumentTitle(title: string): void; 18 | ``` 19 | 20 | Since it reverts `document.title` to previous value, it should be safe to have multiple components using this hook simultaneously. 21 | 22 | 23 | -------------------------------------------------------------------------------- /packages/script/demo/components/WithSuspense/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-no-bind */ 2 | import {useState} from 'react'; 3 | import {useScriptSuspense} from '../../../src'; 4 | 5 | const WithSuspense = () => { 6 | const [index, setIndex] = useState(0); 7 | useScriptSuspense(index === 0 ? undefined : `/${index}.js`); 8 | 9 | return ( 10 |
11 | 12 | 13 |
14 | ); 15 | }; 16 | 17 | export default WithSuspense; 18 | -------------------------------------------------------------------------------- /packages/timeout/docs/useInterval.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useInterval 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Timeout 8 | path: /timeout 9 | order: 3 10 | --- 11 | 12 | # useInterval 13 | 14 | Set an interval to execute callback periodically. 15 | 16 | ```typescript 17 | function useInterval(callback: (() => void) | undefined, time: number): void; 18 | ``` 19 | 20 | To cancel interval, pass a native `time`, `-1` is recommended. 21 | 22 | **Note: `useInterval` does not execute `callback` on initial mount, to trigger it immediately, add an extra `useEffect`.** 23 | 24 | -------------------------------------------------------------------------------- /packages/boolean/docs/useBoolean.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useBoolean 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Boolean 8 | path: /boolean 9 | order: 2 10 | --- 11 | 12 | # useBoolean 13 | 14 | `useBoolean` returns a `[value, methods]` tuple, in which methods are listed as: 15 | 16 | ```typescript 17 | interface BooleanMethods { 18 | // Change value to true 19 | on(): void; 20 | // Change value to false 21 | off(): void; 22 | // Toggle current value, can force update if a boolean argument is provided 23 | toggle(value: unknown): void; 24 | } 25 | ``` 26 | 27 | -------------------------------------------------------------------------------- /packages/previous-value/docs/demo/usePreviousValue.tsx: -------------------------------------------------------------------------------- 1 | import React, {useReducer} from 'react'; 2 | import {Button} from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import {usePreviousValue} from '@huse/previous-value'; 5 | 6 | export default () => { 7 | const [value, increment] = useReducer(v => v + 1, 0); 8 | const previousValue = usePreviousValue(value); 9 | return ( 10 |
11 |

Result: {previousValue === undefined ? value : `${previousValue} -> ${value}`}

12 | 13 |
14 | ); 15 | }; -------------------------------------------------------------------------------- /packages/local-storage/docs/useLocalStorage.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useLocalStorage 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Local Storage 8 | path: /local-storage 9 | order: 2 10 | --- 11 | 12 | # useLocalStorage 13 | 14 | Retrieve value from `localStorage` by key, also returns a function to update it. Value could be any type which can be handled by `JSON.stringify`. 15 | 16 | ```typescript 17 | function useLocalStorage(key: string, initialValue: T): [T, (value: T) => void] 18 | ``` 19 | 20 | Any update to value from other tabs or frames will be observed via `storage` event and sync to state. 21 | 22 | 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "packages/*/src/**/*.ts", 4 | "packages/*/docs/**/*.ts", 5 | "packages/*/docs/**/*.tsx", 6 | ], 7 | "exclude": [ 8 | "packages/**/__tests__/**" 9 | ], 10 | "compilerOptions": { 11 | "jsx": "preserve", 12 | "allowJs": false, 13 | "target": "ES2015", 14 | "module": "CommonJS", 15 | "esModuleInterop": true, 16 | "strict": true, 17 | "noImplicitAny": false, 18 | "sourceMap": true, 19 | "noEmit": false, 20 | "declaration": true, 21 | "skipLibCheck": true, 22 | "moduleResolution": "node" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/scroll-lock/docs/useScrollLock.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useScrollLock 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Scroll Lock 8 | path: /scroll-lock 9 | order: 2 10 | --- 11 | 12 | # useScrollLock 13 | 14 | Pass a `boolean` value to lock scroll on document. 15 | 16 | ```typescript 17 | function useScrollLock(lock: boolean): void 18 | ``` 19 | 20 | When `lock` is `true` scroll will be locked, `overflow` style will be reverted when `lock` becomes `false` or component is unmounted. 21 | 22 | If an other modification of `overflow` style happens after scroll lock, `useScrollLock` will not revert the style. 23 | 24 | 25 | -------------------------------------------------------------------------------- /packages/intended-lazy/src/index.ts: -------------------------------------------------------------------------------- 1 | import {useImperativeHandle, useRef} from 'react'; 2 | 3 | export function useIntendedLazyValue(value: T): () => T { 4 | const ref = useRef(value); 5 | useImperativeHandle(ref, () => value); 6 | const stableGet = useRef(() => ref.current); 7 | return stableGet.current; 8 | } 9 | 10 | // eslint-disable-next-line @typescript-eslint/ban-types 11 | export function useIntendedLazyCallback(fn: T): T { 12 | const ref = useRef(fn); 13 | useImperativeHandle(ref, () => fn); 14 | const stableCallback = useRef((...args: any[]) => ref.current(...args)); 15 | return stableCallback.current as unknown as T; 16 | } 17 | -------------------------------------------------------------------------------- /packages/network/src/index.ts: -------------------------------------------------------------------------------- 1 | import {useEffect} from 'react'; 2 | import {useSwitch} from '@huse/boolean'; 3 | 4 | export function useOnLine(): boolean { 5 | const [onLine, goOnLine, goOffLine] = useSwitch(navigator.onLine ?? true); 6 | useEffect( 7 | () => { 8 | window.addEventListener('online', goOnLine); 9 | window.addEventListener('offline', goOffLine); 10 | 11 | return () => { 12 | window.removeEventListener('online', goOnLine); 13 | window.removeEventListener('offline', goOffLine); 14 | }; 15 | }, 16 | [goOnLine, goOffLine] 17 | ); 18 | 19 | return onLine; 20 | } 21 | -------------------------------------------------------------------------------- /scripts/generate/dependencies.js: -------------------------------------------------------------------------------- 1 | const plain = require('../../packages/boolean/package.json'); 2 | const dom = require('../../packages/click-outside/package.json'); 3 | const demo = require('../../packages/element-size/package.json'); 4 | 5 | const dependencies = Object.assign( 6 | {}, 7 | plain.dependencies, 8 | plain.devDependencies, 9 | dom.dependencies, 10 | dom.devDependencies, 11 | demo.dependencies, 12 | demo.devDependencies 13 | ); 14 | 15 | module.exports = name => { 16 | const version = dependencies[name]; 17 | 18 | if (!version) { 19 | throw new Error(`Cannot find a suitable version for ${name}`); 20 | } 21 | 22 | return version; 23 | }; 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [16.x] 17 | 18 | steps: 19 | - uses: pnpm/action-setup@v2.0.1 20 | with: 21 | version: 6.0.2 22 | - uses: actions/checkout@v2 23 | - name: CI 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: pnpm install 28 | - run: npm run lint 29 | - run: npm run build 30 | - run: npm run test 31 | env: 32 | CI: true 33 | -------------------------------------------------------------------------------- /packages/router/docs/useUpdateSearchParams.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useUpdateSearchParams 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Router 8 | path: /router 9 | order: 7 10 | --- 11 | 12 | # useUpdateSearchParams 13 | 14 | Get a function to update search params via any object. 15 | 16 | ```typescript 17 | interface SearchParamsPatch { 18 | [key: string]: string | number | string[] | number[] | undefined | null; 19 | } 20 | 21 | type UpdateSearchParams = (patch: SearchParamsPatch, options?: NavigateOptions) => void; 22 | 23 | function useUpdateSearchParams(): UpdateSearchParams; 24 | ``` 25 | 26 | `URLSearchParams#toString` is used to stringify search params to search string. -------------------------------------------------------------------------------- /packages/scroll-into-view/src/index.ts: -------------------------------------------------------------------------------- 1 | import {useEffect, RefObject, useRef} from 'react'; 2 | 3 | export function useScrollIntoView( 4 | ref: RefObject, 5 | active: boolean = true, 6 | options: boolean | ScrollIntoViewOptions = {behavior: 'smooth'} 7 | ): void { 8 | const scrollOptions = useRef(options); 9 | useEffect( 10 | () => { 11 | scrollOptions.current = options; 12 | }, 13 | [options] 14 | ); 15 | useEffect( 16 | () => { 17 | if (ref.current && active) { 18 | ref.current.scrollIntoView(scrollOptions.current); 19 | } 20 | }, 21 | [ref, active] 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /packages/derived-state/src/index.ts: -------------------------------------------------------------------------------- 1 | import {useState, Dispatch, SetStateAction} from 'react'; 2 | 3 | export type Derive = (propValue: P, stateValue: S | undefined) => S; 4 | 5 | export function useDerivedState( 6 | propValue: P, 7 | compute: Derive = v => v as unknown as S 8 | ): [S, Dispatch>] { 9 | const [previousPropValue, setPreviousPropValue] = useState(propValue); 10 | const [value, setValue] = useState(() => compute(propValue, undefined)); 11 | 12 | if (previousPropValue !== propValue) { 13 | setValue(state => compute(propValue, state)); 14 | setPreviousPropValue(propValue); 15 | } 16 | 17 | return [value, setValue]; 18 | } 19 | -------------------------------------------------------------------------------- /packages/intersection/docs/demo/useOnScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useOnScreen} from '@huse/intersection'; 3 | import './useOnScreen.less'; 4 | 5 | export default () => { 6 | const [ref, isOnScreen] = useOnScreen({rootMargin: '10px', threshold: 0.3}); 7 | return ( 8 | <> 9 |

Scroll Your Web 👇

10 |

11 | Visual port is currently 👉 12 | {isOnScreen ? 'on' : 'out of'} screen 13 |

14 |
15 | This is visual port 16 |
17 | 18 | ); 19 | }; -------------------------------------------------------------------------------- /packages/click-outside/docs/useClickOutside.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useClickOutside 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Click Outside 8 | path: /click-outside 9 | order: 2 10 | --- 11 | 12 | # useClickOutside 13 | 14 | By passing a `ref` object containing an element, `callback` will be triggered when a click happens outside that element. 15 | 16 | ```typescript 17 | function useClickOutside(ref: RefObject, callback: (e: MouseEvent | TouchEvent) => void) 18 | ``` 19 | 20 | This hook often used in cases where a modal or a dropdown should hide when clicked outside. 21 | 22 | It also works in a touch device, `touchstart` will be used. 23 | 24 | 25 | -------------------------------------------------------------------------------- /packages/input-value/docs/useInputValue.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useInputValue 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Input Value 8 | path: /input-value 9 | order: 2 10 | --- 11 | 12 | # useInputValue 13 | 14 | To get rid of the duplication of `e => setState(e.target.value)`, `useInputValue` returns an object containing both `value` and change event aware `onChange`. 15 | 16 | ```typescript 17 | interface InputValueState { 18 | value: string; 19 | onChange(e: ChangeEvent): void; 20 | } 21 | function useInputValue(initialValue: string = ''): InputValueState; 22 | ``` 23 | 24 | This hook is better used with `{...props}` syntax in JSX. 25 | 26 | -------------------------------------------------------------------------------- /packages/performance/docs/useLayoutTiming.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useLayoutTiming 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Performance 8 | path: /performance 9 | order: 3 10 | --- 11 | 12 | # useLayoutTiming 13 | 14 | This hooks track a meaningful layout once. 15 | 16 | ```typescript 17 | interface TimeRange { 18 | start: number; 19 | end: number; 20 | ellapsed: number; 21 | } 22 | 23 | function useLayoutTiming(callback: (timing: TimeRange) => void, meaningful?: boolean): void 24 | ``` 25 | 26 | `meaningful` is `true` by default in case the first layout will be reported, 27 | you can dynamiclly pass it to record a more meaningful layout time. 28 | 29 | -------------------------------------------------------------------------------- /packages/poll/docs/demo/usePoll.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback} from 'react'; 2 | import {Spin} from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import {usePoll} from '@huse/poll'; 5 | 6 | export default () => { 7 | const generateRandom = useCallback( 8 | // Delay for 1.5s and resolve with a random value 9 | () => new Promise(resolve => setTimeout(() => resolve(Math.random()), 1500)), 10 | [] 11 | ); 12 | // Polls every 10s 13 | const [value, pendingCount] = usePoll(generateRandom, 10 * 1000); 14 | return ( 15 |
16 | {value} 17 | {!!pendingCount && } 18 |
19 | ); 20 | }; -------------------------------------------------------------------------------- /packages/previous-value/docs/usePreviousValue.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: usePreviousValue 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Previous Value 8 | path: /previous-value 9 | order: 2 10 | --- 11 | 12 | # usePreviousValue 13 | 14 | Prevoius value if sometimes useful in react development, like to derive a state, 15 | or to determine transition duration on how value is changed. 16 | 17 | This hook returns the previous value of given argument. 18 | 19 | ```typescript 20 | function usePreviousValue(value: T): T | undefined 21 | ``` 22 | 23 | **Note: this hook is not designed to get the "previous different version", the previous value may be the same of given value.** 24 | 25 | -------------------------------------------------------------------------------- /packages/debug/docs/demo/useRenderTimes.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import {Button} from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import {useRenderTimes, useChangeTimes, useUpdateCause} from '@huse/debug'; 5 | 6 | export default () => { 7 | const [value, setValue] = useState(0); 8 | const renderTimes = useRenderTimes(); 9 | return ( 10 | <> 11 | 12 | 13 | 14 | 15 | ); 16 | }; -------------------------------------------------------------------------------- /docs/docs/packages.en-US.md: -------------------------------------------------------------------------------- 1 | # Project structure 2 | 3 | This is a typical pnpm workspace based monorepo, each hook creates a package in `packages` folder, the folder name is dash-cased and without the `use` prefix. 4 | 5 | Package name must comform a format of `@huse/foo-bar`. 6 | 7 | ```bash 8 | 9 | packages 10 | │ ├── action-pending 11 | │ ├── window-size 12 | │ ├── foo-bar 13 | │ └── xxx-xxx 14 | 15 | ``` 16 | 17 | Each package should named export at least one hook like: 18 | 19 | ```js 20 | import { useInputValue } from '@huse/input-value'; 21 | ``` 22 | 23 | ## Unit Tests 24 | Unit tests are recommended, they are placed inside `src/__tests__` folder with an extension of `.test.js`, we highly recommend a 100% of branch coverage. 25 | -------------------------------------------------------------------------------- /packages/debug/docs/demo/useChangeTimes.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import {Button} from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import {useRenderTimes, useChangeTimes, useUpdateCause} from '@huse/debug'; 5 | 6 | export default () => { 7 | const [value, setValue] = useState(0); 8 | const renderTimes = useChangeTimes(value); 9 | return ( 10 | <> 11 | 12 | 13 | 14 | 15 | ); 16 | }; -------------------------------------------------------------------------------- /packages/element-size/docs/useElementSize.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useElementSize 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Element Size 8 | path: /element-size 9 | order: 3 10 | --- 11 | 12 | # useElementSize 13 | 14 | Observes and returns the offset size of an element. 15 | 16 | 17 | ```typescript 18 | export interface Size { 19 | width: number; // offsetWidth 20 | height: number; // offsetHeight 21 | } 22 | 23 | type ElementResizeCallback = (element: HTMLElement | null) => void; 24 | 25 | function useElementSize(): [ElementResizeCallback, Size | undefined]; 26 | ``` 27 | 28 | The initial size is `undefined` and will be updated on mount anytime element is resized. 29 | 30 | -------------------------------------------------------------------------------- /packages/network/docs/demo/useOnLine.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {CheckCircleOutlined, CloseCircleOutlined} from '@ant-design/icons'; 3 | import 'antd/dist/antd.min.css'; 4 | import {useOnLine} from '@huse/network'; 5 | 6 | export default () => { 7 | const isOnLine = useOnLine(); 8 | return ( 9 | <> 10 | {isOnLine ? : } 11 | You are currently 12 | { 13 | isOnLine 14 | ? ONLINE 15 | : OFFLINE 16 | } 17 | 18 | 19 | ) 20 | }; -------------------------------------------------------------------------------- /packages/debounce/docs/useDebouncedEffect.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useDebouncedEffect 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Debounce 8 | path: /debounce 9 | order: 2 10 | --- 11 | 12 | # useDebouncedEffect 13 | 14 | Runs a callback when a value changed with a delay, effect will be canceled if value is changed again within delay. 15 | 16 | ```typescript 17 | function useDebouncedEffect(callback: () => void | (() => void), value: T, wait: number): void 18 | ``` 19 | 20 | Unlike `useEffect`, `useDebouncedEffect` accepts only one dependency value. 21 | 22 | Still `callback` can return a clean-up function, this function is called **immediately when value changes without delay**. 23 | 24 | -------------------------------------------------------------------------------- /packages/optimistic/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: README 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Optimistic 8 | path: /optimistic 9 | order: 1 10 | --- 11 | 12 | # Optimistic 13 | 14 | Provides a set of react hooks to help manage optimistic states. 15 | 16 | As previously stated in [redux-optimistic-thunk](https://github.com/ecomfe/redux-optimistic-thunk#why-this-middleware), 17 | manually managing optimistic states, commits, rollbacks and transactions is not ideal model of state management. 18 | React hooks provide powers to manage states in a more functional way, and this library aimed to build optimistic functions above hooks. 19 | 20 | **This library requires [ES6 Generators](https://caniuse.com/#feat=es6-generators) to work.** -------------------------------------------------------------------------------- /packages/debounce/docs/demo/useDebouncedValue.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import { Input } from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import { useDebouncedValue } from '@huse/debounce'; 5 | 6 | export default () => { 7 | const [value, setValue] = useState(''); 8 | const debouncedValue = useDebouncedValue(value, 200); // debounced update 200ms 9 | return ( 10 | <> 11 |
12 | setValue(e.target.value)} /> 13 |
14 |
15 | Current Value: {debouncedValue} 16 |
17 | 18 | ); 19 | }; -------------------------------------------------------------------------------- /packages/index/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: huse 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | --- 7 | 8 | # huse 9 | 10 | This is an index package for all [@huse packages](https://www.npmjs.com/search?q=%40huse) **except `@huse/router`**. 11 | 12 | **NOTE: This package does not include `@huse/router` since it requires `react-router-dom` which is not commonly installed.** 13 | 14 | Refer to [github repository](https://github.com/ecomfe/react-hooks/tree/master/packages) to find all hooks. 15 | 16 | ## Install 17 | 18 | ```shell 19 | npm install huse 20 | ``` 21 | 22 | Or with yarn: 23 | 24 | ```shell 25 | yarn add huse 26 | ``` 27 | 28 | ## Version 29 | 30 | Version of this packages increases on each publish, thus version may not align with any individual packages. 31 | -------------------------------------------------------------------------------- /packages/hover/docs/demo/useHover2.tsx: -------------------------------------------------------------------------------- 1 | import React, {useReducer, useCallback} from 'react'; 2 | import {useHover} from '@huse/hover'; 3 | 4 | export default () => { 5 | const [isHover, hoverCallbacks] = useHover({delay: 2000}); 6 | const [hoverTimes, increaseHoverTimes] = useReducer(v => v + 1, 0); 7 | const enter = useCallback( 8 | e => { 9 | increaseHoverTimes(); 10 | hoverCallbacks.onMouseEnter(e); 11 | }, 12 | [] 13 | ); 14 | return ( 15 |
16 | Hovered 17 | {hoverTimes} times 18 | {isHover && '(hovered)'} 19 |
20 | ); 21 | }; -------------------------------------------------------------------------------- /packages/collection/docs/useSet.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useSet 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Collection 8 | path: /collection 9 | order: 3 10 | --- 11 | 12 | # useSet 13 | 14 | Encapsulate `Set` type into methods via `useMethods`, contains methods below: 15 | 16 | ```typescript 17 | { 18 | add(item: T): void; 19 | addAll(items: Iterable): void; 20 | delete(item: T): void; 21 | deleteAll(items: Iterable): void; 22 | clear(): void; 23 | } 24 | ``` 25 | 26 | Works like any methods hook. 27 | 28 | ```javascript 29 | import { useSet } from '@huse/collection'; 30 | 31 | export default () => { 32 | const [set, methods] = useSet([1, 2, 3]); 33 | 34 | return ( 35 | // .... do something 36 | ); 37 | }; 38 | ``` -------------------------------------------------------------------------------- /packages/action-pending/docs/useActionPending.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useActionPending 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Action Pending 8 | path: /action-pending 9 | order: 2 10 | --- 11 | 12 | # useActionPending 13 | 14 | By providing an async function, this hook creates a wrapped version with a number indicating how many pending calls are on the fly. 15 | 16 | ```typescript 17 | type AsyncFunction = (...args: any[]) => Promise; 18 | 19 | function useActionPending
(action: A): [A, number] 20 | ``` 21 | 22 | The second value of returned tuple is the `pendingCount`, a simple `!!pendingCount` can be used to check whether there is any unfinished calls and motivates to a loading UI. 23 | 24 | -------------------------------------------------------------------------------- /packages/immer/docs/useImmerState.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useImmerState 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Immer 8 | path: /immer 9 | order: 2 10 | --- 11 | 12 | # useImmerState 13 | 14 | Like `useState` but give the ability to update state by directly mutate it. 15 | 16 | ```typescript 17 | type ImmerStateProducer = (state: S) => S | void; 18 | type SetImmerState = (next: S | ImmerStateProducer) => void; 19 | type ImmerState = [S, SetImmerState]; 20 | function useImmerState(initialState: S | (() => S)): ImmerState; 21 | ``` 22 | 23 | This works exactly the same as `useState` with a single difference that when a function is passed to `setState`, it can mutate state directly. 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /packages/intersection/docs/useOnScreenCallback.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useOnScreenCallback 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Intersection 8 | path: /intersection 9 | order: 4 10 | --- 11 | 12 | # useOnScreenCallback 13 | 14 | This is a fundamental hook which triggers a callback when element intersects with screen. 15 | 16 | ```typescript 17 | type OnScreenOptions = Omit; 18 | function useOnScreenCallback(callback: (entry: IntersectionObserverEntry) => void, options?: OnScreenOptions): EffectRef; 19 | ``` 20 | 21 | `callback` is triggered whenever element is into or out of screen. 22 | 23 | Most of the time `useOnScreen` and `useOnScreenValue` are enough, those hooks leave the ability to extend custom logics with screen intersection. -------------------------------------------------------------------------------- /packages/collection/docs/useMap.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useMap 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Collection 8 | path: /collection 9 | order: 4 10 | --- 11 | 12 | # useMap 13 | 14 | Encapsulate `Map` type into methods via `useMethods`, contains methods below: 15 | 16 | ```typescript 17 | { 18 | set(key: K, value: V): void; 19 | setAll(entries: Iterable<[K, V]>): void; 20 | delete(key: K): void; 21 | deleteAll(keys: Iterable): void; 22 | clear(): void; 23 | } 24 | ``` 25 | 26 | Works like any methods hook. 27 | 28 | ```javascript 29 | import { useMap } from '@huse/collection'; 30 | 31 | export default () => { 32 | const [map, methods] = useMap([[1, 2], [3, 4]]); 33 | 34 | return ( 35 | // .... do something 36 | ); 37 | }; 38 | ``` -------------------------------------------------------------------------------- /packages/intersection/docs/useOnScreen.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useOnScreen 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Intersection 8 | path: /intersection 9 | order: 2 10 | --- 11 | 12 | # useOnScreen 13 | 14 | Returns a boolean indicating whether element is currently on screen. 15 | 16 | ```typescript 17 | type OnScreenOptions = Omit; 18 | function useOnScreen({rootMargin, threshold}: OnScreenOptions = {}): [EffectRef, boolean]; 19 | ``` 20 | 21 | This hook only checks intersection between element and the root element (`documentElement` in browser). 22 | 23 | **Note: `useOnScreen` requires `IntersectionObserver` to work, without `IntersectionObserver` it supposes the element is always on screen.** 24 | 25 | -------------------------------------------------------------------------------- /packages/immer/docs/useImmerReducer.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useImmerReducer 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Immer 8 | path: /immer 9 | order: 3 10 | --- 11 | 12 | # useImmerReducer 13 | 14 | A `useReducer` bound to immer, direct mutation of state is allowed inside reducer. 15 | 16 | ```typescript 17 | type ImmerReducer = (state: S, action: A) => S | void; 18 | function useImmerReducer(reducer: ImmerReducer, initialState: S, initializer?: () => S): [S, Dispatch]; 19 | ``` 20 | 21 | Some differences with `useReducer`: 22 | 23 | 1. Requires a single `action` argument in reducer. 24 | 2. reducer can mutate state directly. 25 | 3. `initializer` won't receive `initialState` as its argument. 26 | 27 | -------------------------------------------------------------------------------- /packages/performance/docs/demo/useLayoutTiming.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useReducer } from 'react'; 2 | import { Button } from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import { useLayoutTiming } from '@huse/performance'; 5 | 6 | export default () => { 7 | const [timing, setTiming] = useState(); 8 | const [userClicked, setClicked] = useReducer(() => true, false); 9 | useLayoutTiming(setTiming, userClicked); 10 | return ( 11 | <> 12 |
13 | 14 |
15 | {timing &&

{timing.end} (end) - {timing.start} (start) = {timing.ellapsed} (ellapsed)

} 16 | 17 | ); 18 | }; -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Document 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | deploy: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | matrix: 14 | node-version: [16.x] 15 | 16 | steps: 17 | - uses: pnpm/action-setup@v2.0.1 18 | with: 19 | version: 6.0.2 20 | - uses: actions/checkout@v2 21 | - name: Build 22 | uses: actions/setup-node@v1 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - run: pnpm install 26 | - run: npm run build 27 | - run: npm run doc:build 28 | env: 29 | CI: true 30 | - name: Deploy 31 | uses: peaceiris/actions-gh-pages@v3 32 | with: 33 | github_token: ${{ secrets.GITHUB_TOKEN }} 34 | publish_dir: ./dist 35 | -------------------------------------------------------------------------------- /packages/local-storage/docs/demo/useLocalStorage.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState, useCallback} from 'react'; 2 | import {Button, Input} from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import {useLocalStorage} from '@huse/local-storage'; 5 | 6 | export default () => { 7 | const [storageValue, setValueToStorage] = useLocalStorage('memo', ''); 8 | const [value, setValue] = useState(storageValue); 9 | const commitValue = useCallback( 10 | () => setValueToStorage(value), 11 | [setValueToStorage, value] 12 | ); 13 | return ( 14 | <> 15 | setValue(e.target.value)} /> 16 | 17 | 18 | ); 19 | }; -------------------------------------------------------------------------------- /packages/window-size/src/__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-void */ 2 | import {renderHook, act} from '@testing-library/react-hooks'; 3 | import {useWindowSize} from '../index'; 4 | 5 | test('initial size', () => { 6 | const {result} = renderHook(() => useWindowSize()); 7 | expect(typeof result.current.innerWidth).toBe('number'); 8 | expect(typeof result.current.innerHeight).toBe('number'); 9 | expect(typeof result.current.outerWidth).toBe('number'); 10 | expect(typeof result.current.outerWidth).toBe('number'); 11 | }); 12 | 13 | test('notify size change', () => { 14 | const {result} = renderHook(() => useWindowSize()); 15 | global.innerWidth = 1000; 16 | act(() => void window.dispatchEvent(new Event('resize'))); 17 | expect(result.current.innerWidth).toBe(1000); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/debounce/docs/demo/useDebouncedEffect.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import { Input } from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import { useDebouncedEffect } from '@huse/debounce'; 5 | 6 | export default () => { 7 | const [value, setValue] = useState(''); 8 | const [message, setMessage] = useState(''); 9 | useDebouncedEffect( 10 | () => { 11 | value && setMessage(`Value updated to ${value}`); 12 | }, 13 | value, 14 | 200 15 | ); 16 | return ( 17 | <> 18 |
19 | setValue(e.target.value)} /> 20 |
21 |
{message}
22 | 23 | ); 24 | }; -------------------------------------------------------------------------------- /packages/merged-ref/docs/useMergedRef.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useMergedRef 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Merged Ref 8 | path: /merged-ref 9 | order: 2 10 | --- 11 | 12 | # useMergedRef 13 | 14 | In react, refs can be a mutation container typed `Ref`, or a callback typed `CallbackRef`. 15 | In case you need to use multiple refs on a single element like, this hook helps you to merge them into a single `CallbackRef`. 16 | 17 | ```typescript 18 | type RefLike = Ref | null | undefined; 19 | 20 | function useMergedRef(refs: Array>): RefCallback 21 | ``` 22 | 23 | In the ecosystem of react, many custom hooks return callback ref, in order to serve multiple functions on the same element, 24 | `useMergedRef` is introduced. 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/input-value/docs/demo/useInputValue.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Input, Form} from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import {useInputValue} from '@huse/input-value'; 5 | 6 | export default () => { 7 | const name = useInputValue(''); 8 | const age = useInputValue(10); 9 | const layout = { 10 | labelCol: { span: 3 }, 11 | wrapperCol: { span: 21 }, 12 | }; 13 | return ( 14 | <> 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | ); 25 | }; -------------------------------------------------------------------------------- /packages/debug/docs/useChangeTimes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useChangeTimes 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Debug 8 | path: /debug 9 | order: 3 10 | --- 11 | 12 | # useChangeTimes 13 | 14 | Like `useRenderTimes` but only increment when a value is actually changed, this value is zero based. 15 | 16 | ```typescript 17 | function useChangeTimes(value: T): number 18 | ``` 19 | 20 | 21 | 22 | 23 | This hook identifies "change" by reference, to inspect why a value has changed, try `useUpdateCause` hook. 24 | 25 | ```javascript 26 | import {useChangeTimes} from '@huse/debug'; 27 | 28 | const App = props => { 29 | const renderTimes = useChangeTimes(props.foo); 30 | 31 | console.log(renderTimes); 32 | 33 | return ( 34 | // ... 35 | ); 36 | }; 37 | ``` -------------------------------------------------------------------------------- /packages/boolean/docs/demo/useBoolean.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button, Switch } from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import './useBoolean.less'; 5 | import { useBoolean } from '@huse/boolean'; 6 | 7 | export default () => { 8 | const [value, {on, off, toggle}] = useBoolean(); 9 | return ( 10 | <> 11 |
12 | 13 | 14 | 15 |
16 |
17 |

Current value:

18 |
19 | 20 | 21 | ) 22 | }; -------------------------------------------------------------------------------- /packages/boolean/src/index.ts: -------------------------------------------------------------------------------- 1 | import {useMethodsNative} from '@huse/methods'; 2 | 3 | const reducers = { 4 | on() { 5 | return true; 6 | }, 7 | off() { 8 | return false; 9 | }, 10 | toggle(prevState: boolean, arg: unknown) { 11 | return typeof arg === 'boolean' ? arg : !prevState; 12 | }, 13 | }; 14 | 15 | export function useBoolean(initialValue: boolean = false) { 16 | return useMethodsNative(reducers, initialValue); 17 | } 18 | 19 | export function useSwitch(initialValue: boolean = false) { 20 | const [value, {on, off, toggle}] = useBoolean(initialValue); 21 | return [value, on, off, toggle] as const; 22 | } 23 | 24 | export function useToggle(initialValue: boolean = false) { 25 | const [value, {toggle}] = useBoolean(initialValue); 26 | return [value, toggle] as const; 27 | } 28 | -------------------------------------------------------------------------------- /packages/intersection/demo/entries/index.js: -------------------------------------------------------------------------------- 1 | import {render} from 'react-dom'; 2 | import {useOnScreenValue} from '../../src'; 3 | 4 | const EMPTY_GIF = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; 5 | 6 | const Fly = () => { 7 | const [listenOnScreen, src] = useOnScreenValue( 8 | 'https://mildaintrainings.com/wp-content/uploads/2017/11/react-logo.png', 9 | EMPTY_GIF 10 | ); 11 | const style = { 12 | width: 300, 13 | height: 300, 14 | position: 'absolute', 15 | top: '120vh', 16 | left: '45vw', 17 | marginBottom: 60, 18 | }; 19 | 20 | return ( 21 | 22 | ); 23 | }; 24 | 25 | render( 26 | , 27 | document.body.appendChild(document.createElement('div')) 28 | ); 29 | -------------------------------------------------------------------------------- /packages/scroll-position/docs/demo/useScrollPosition.tsx: -------------------------------------------------------------------------------- 1 | import React, {useRef} from 'react'; 2 | import {useScrollPosition} from '@huse/scroll-position'; 3 | 4 | export default () => { 5 | const ref = useRef(null); 6 | const position = useScrollPosition(ref); 7 | const gradient = 'radial-gradient(circle at 10% 20%, rgb(6, 123, 239) 14.2%, rgb(219, 115, 249) 89.5%)'; 8 | console.log('position---->', position); 9 | return ( 10 | <> 11 |

Scroll in the color block

12 |
13 |
14 |
15 |

scrollTop: {position.y}

16 |

scrollLeft: {position.x}

17 | 18 | ) 19 | }; -------------------------------------------------------------------------------- /packages/element-size/docs/useElementResize.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useElementResize 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Element Size 8 | path: /element-size 9 | order: 2 10 | --- 11 | 12 | # useElementResize 13 | 14 | Trigger a callback when element resizes. 15 | 16 | ```typescript 17 | type ElementResizeCallback = (element: HTMLElement | null) => void; 18 | 19 | function useElementResize(callback: (element: HTMLElement) => void): ElementResizeCallback; 20 | ``` 21 | 22 | To ensure all element changed are captured even with the change of element type (like from `
` to ``), 23 | `useElementResize` returns a callback ref, you are required to pass it via `ref` prop to an DOM element. 24 | 25 | **Note: `useElementResize` does not trigger callback on initial mount.** 26 | 27 | 28 | -------------------------------------------------------------------------------- /packages/timeout/docs/demo/useInterval.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import {Slider} from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import {useTimeout, useInterval} from '@huse/timeout'; 5 | 6 | export default () => { 7 | const [theme, setTheme] = useState('light'); 8 | // Switch theme every 5s 9 | useInterval( 10 | () => setTheme(theme => (theme === 'light' ? 'dark' : 'light')), 11 | 5 * 1000 12 | ); 13 | const style = { 14 | height: 40, 15 | lineHeight: '40px', 16 | textAlign: 'center', 17 | backgroundColor: theme === 'light' ? '#fff' : '#222', 18 | color: theme === 'light' ? '#666' : '#f4f5f6', 19 | }; 20 | return ( 21 | <> 22 |
23 | Hello World 24 |
25 | 26 | ); 27 | }; -------------------------------------------------------------------------------- /packages/transition-state/docs/useTransitionState.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useTransitionState 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Transition State 8 | path: /transition-state 9 | order: 2 10 | --- 11 | 12 | # useTransitionState 13 | 14 | Like `useState`, `useTransitionState` returns a `[value, setValue]` tuple, however when a new value takes effect via `setValue`, `value` will return to its default value after given duration. 15 | 16 | This hook can be commonly used to show a temporary message. 17 | 18 | ```typescript 19 | function useTransitionState(defaultValue: S, defaultDuration?: number) 20 | ``` 21 | 22 | The `setValue` takes an extra `duration` argument to specify the time before value change back to default, it defaults to `defaultDuration` argument of `useTransitionState`. 23 | 24 | 25 | -------------------------------------------------------------------------------- /packages/document-title/docs/demo/useDocumentTitle.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import { Input,Row,Col } from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import {useDocumentTitle} from '@huse/document-title'; 5 | 6 | export default () => { 7 | const [title, setTitle] = useState(''); 8 | useDocumentTitle(title); 9 | return ( 10 | <> 11 |

change document.title

12 | 13 | 14 | setTitle(e.target.value)} /> 15 | 16 | 17 | 18 | 19 |

(Leave this page will reset document.title)

20 |
21 | 22 | ); 23 | }; -------------------------------------------------------------------------------- /packages/media/src/index.ts: -------------------------------------------------------------------------------- 1 | import {useEffect, useState} from 'react'; 2 | 3 | const matchMedia = (query: string) => { 4 | const watcher = window.matchMedia(query); 5 | return { 6 | watcher, 7 | matches: watcher.matches, 8 | }; 9 | }; 10 | 11 | export function useMedia(query: string): boolean { 12 | const [matched, setMatched] = useState(() => matchMedia(query).matches); 13 | useEffect( 14 | () => { 15 | const {watcher} = matchMedia(query); 16 | const onChange = () => setMatched(!!watcher.matches); 17 | watcher.addListener(onChange); 18 | return () => watcher.removeListener(onChange); 19 | }, 20 | [query] 21 | ); 22 | return matched; 23 | } 24 | 25 | export function usePreferDarkMode(): boolean { 26 | return useMedia('(prefers-color-scheme: dark)'); 27 | } 28 | -------------------------------------------------------------------------------- /packages/scroll-into-view/demo/entries/index.less: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | } 5 | 6 | * { 7 | box-sizing: border-box; 8 | } 9 | 10 | .header { 11 | position: fixed; 12 | top: 0; 13 | left: 0; 14 | width: 100vw; 15 | padding: 0 40px; 16 | height: 60px; 17 | display: flex; 18 | align-items: center; 19 | background-color: #f6f6f6; 20 | } 21 | 22 | .anchor { 23 | .centered-line(30px); 24 | 25 | width: 60px; 26 | margin-right: 20px; 27 | text-align: center; 28 | color: #fff; 29 | cursor: pointer; 30 | 31 | &:hover { 32 | color: #ccc; 33 | } 34 | } 35 | 36 | .block { 37 | width: 100vw; 38 | height: 100vh; 39 | display: flex; 40 | align-items: center; 41 | justify-content: center; 42 | font-size: 48px; 43 | font-weight: bold; 44 | color: #fff; 45 | } 46 | -------------------------------------------------------------------------------- /packages/scroll-into-view/src/__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | import {useRef} from 'react'; 2 | import {render, fireEvent} from '@testing-library/react'; 3 | import {renderHook} from '@testing-library/react-hooks'; 4 | import {useForceUpdate} from '../index'; 5 | 6 | test('valid return type', () => { 7 | const {result} = renderHook(() => useForceUpdate()); 8 | expect(typeof result.current).toBe('function'); 9 | }); 10 | 11 | test('will force update', () => { 12 | const Foo = () => { 13 | const times = useRef(0); 14 | times.current++; 15 | const forceUpdate = useForceUpdate(); 16 | 17 | return
{times.current}
; 18 | }; 19 | 20 | const {container} = render(); 21 | const root = container.querySelector('div'); 22 | fireEvent.click(root); 23 | expect(root.textContent).toBe('2'); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/click-outside/src/__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-no-bind */ 2 | import {useRef} from 'react'; 3 | import {render, fireEvent} from '@testing-library/react'; 4 | import {useClickOutside} from '../index'; 5 | 6 | const Foo = ({onOutside}) => { 7 | const ref = useRef(); 8 | useClickOutside(ref, onOutside); 9 | return ; 10 | }; 11 | 12 | test('click inside', () => { 13 | const counter = {value: 1}; 14 | const {baseElement} = render( counter.value++} />); 15 | fireEvent.mouseUp(baseElement.querySelector('#root')); 16 | expect(counter.value).toBe(1); 17 | }); 18 | 19 | test('click outside', () => { 20 | const counter = {value: 1}; 21 | render( counter.value++} />); 22 | fireEvent.mouseUp(document.body); 23 | expect(counter.value).toBe(2); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/element-size/docs/demo/useElementResize.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState, useReducer, useCallback} from 'react'; 2 | import {useElementResize, useElementSize} from '@huse/element-size'; 3 | 4 | export default () => { 5 | const [height, increaseHeight] = useReducer(v => v + 20, 60); 6 | const [size, setSize] = useState([0, 0]); 7 | const observeSize = useCallback( 8 | element => setSize([element.offsetWidth, element.offsetHeight]), 9 | [] 10 | ); 11 | const ref = useElementResize(observeSize); 12 | return ( 13 |
18 | {size[0]} x {size[1]} (click to increase height) 19 |
20 | ); 21 | }; -------------------------------------------------------------------------------- /packages/script/settings.js: -------------------------------------------------------------------------------- 1 | exports.addition = () => { 2 | return { 3 | devServer: { 4 | before(app) { 5 | const serveStaticScriptByIndex = index => (req, res) => { 6 | const body = ` 7 | window.dynamicScripts = window.dynamicScripts || {}; 8 | window.dynamicScripts[${index}] = Math.random(); 9 | `; 10 | res.contentType('.js'); 11 | res.end(body); 12 | }; 13 | 14 | app.get('/1.js', serveStaticScriptByIndex(1)); 15 | app.get('/2.js', serveStaticScriptByIndex(2)); 16 | app.get('/3.js', serveStaticScriptByIndex(3)); 17 | app.get('/4.js', serveStaticScriptByIndex(4)); 18 | }, 19 | }, 20 | }; 21 | }; 22 | 23 | exports.plugins = []; 24 | -------------------------------------------------------------------------------- /packages/media/docs/demo/useMedia.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useMedia, usePreferDarkMode} from '@huse/media'; 3 | 4 | export default () => { 5 | const isMobile = useMedia('only screen and (min-device-width : 320px) and (max-device-width : 480px)'); 6 | const isPad = useMedia('only screen and (min-device-width : 768px) and (max-device-width : 1024px)'); 7 | const isDesktop = useMedia('only screen and (min-width : 1224px)'); 8 | const deviceType = (() => { 9 | if (isMobile) { 10 | return 'Mobile'; 11 | } 12 | if (isPad) { 13 | return 'Pad'; 14 | } 15 | if (isDesktop) { 16 | return 'Desktop'; 17 | } 18 | return 'Others'; 19 | })(); 20 | return ( 21 |

22 | Your device: {deviceType} 23 |

24 | ); 25 | }; -------------------------------------------------------------------------------- /packages/media/demo/entries/index.js: -------------------------------------------------------------------------------- 1 | import {render} from 'react-dom'; 2 | import {useMedia, usePreferDarkMode} from '../../src'; 3 | 4 | const App = () => { 5 | const isMobile = useMedia('only screen and (min-device-width : 320px) and (max-device-width : 480px)'); 6 | const isPad = useMedia('only screen and (min-device-width : 768px) and (max-device-width : 1024px)'); 7 | const isDarkMode = usePreferDarkMode(); 8 | 9 | return ( 10 |
11 |

12 | Possible Device:  13 | {isMobile ? 'Phone' : (isPad ? 'Pad' : 'Desktop')} 14 |

15 |

16 | Color Scheme:  17 | {isDarkMode ? 'Dark' : 'Light'} 18 |

19 |
20 | ); 21 | }; 22 | 23 | render( 24 | , 25 | document.body.appendChild(document.createElement('div')) 26 | ); 27 | -------------------------------------------------------------------------------- /packages/collection/docs/demo/useArray.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import './useArray.less' 5 | import { useArray } from '@huse/collection'; 6 | 7 | const getRandomColor = function () { 8 | return '#' + Math.floor(Math.random()*16777215).toString(16); 9 | }; 10 | export default () => { 11 | const [array, methods] = useArray(() => Array.from({length: 3}, () => Math.random())); 12 | const renderItem = (value, i) => ( 13 |
14 | #{i}: {value} 15 |
16 | ); 17 | return ( 18 | <> 19 |
20 | 21 |
22 | {array.map(renderItem)} 23 | 24 | ); 25 | }; -------------------------------------------------------------------------------- /packages/number/docs/demo/useCounter.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Button,Row, Col} from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import {useCounter} from '@huse/number'; 5 | 6 | export default () => { 7 | const [value, {increment, decrement, reset}] = useCounter(3); 8 | console.log(value); 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | }; -------------------------------------------------------------------------------- /packages/document-event/docs/useDocumentEvent.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useDocumentEvent 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Document Event 8 | path: /document-event 9 | order: 2 10 | --- 11 | 12 | # useDocumentEvent 13 | 14 | This hook will register a event listener on `document` on component mount, and unlisten that event on unmount. 15 | 16 | ```typescript 17 | type EventNames = keyof DocumentEventMap; 18 | 19 | type DocumentEventHandler = (e: DocumentEventMap[K]) => any; 20 | 21 | function useDocumentEvent(eventName: K, fn: DocumentEventHandler, options?: boolean | AddEventListenerOptions): void 22 | ``` 23 | 24 | The event handler passed to `useDocumentEvent` is not forced to be reference equal, this means you are allowed to use a function expression without `useCallback` to memoize it. 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/debounce/docs/useDebouncedCallback.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useDebouncedCallback 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Debounce 8 | path: /debounce 9 | order: 4 10 | --- 11 | 12 | # useDebouncedCallback 13 | 14 | Simply wrap a callback to a debounced one. 15 | 16 | ```typescript 17 | function useDebouncedCallback(callback: C, wait: number): C 18 | ``` 19 | 20 | Note all queued invocation will be canceled when component unmounts and when either `callback` or `wait` is changed. 21 | 22 | ```javascript 23 | import {useState} from 'react'; 24 | import {useDebouncedCallback} from '@huse/debounce'; 25 | 26 | const [list, setList] = useState([]); 27 | 28 | // invokes after 200ms wait 29 | const search = useDebouncedCallback( 30 | async e => { 31 | const items = await findByKeyword(e.target.value); 32 | setList(items); 33 | }, 34 | 200 35 | ); 36 | ``` -------------------------------------------------------------------------------- /packages/document-event/src/index.ts: -------------------------------------------------------------------------------- 1 | import {useRef, useLayoutEffect} from 'react'; 2 | 3 | type EventNames = keyof DocumentEventMap; 4 | 5 | type DocumentEventHandler = (e: DocumentEventMap[K]) => any; 6 | 7 | export function useDocumentEvent( 8 | eventName: K, 9 | fn: DocumentEventHandler, 10 | options?: boolean | AddEventListenerOptions 11 | ) { 12 | const handler = useRef(fn); 13 | useLayoutEffect( 14 | () => { 15 | handler.current = fn; 16 | }, 17 | [fn] 18 | ); 19 | useLayoutEffect( 20 | () => { 21 | const trigger: DocumentEventHandler = e => handler.current(e); 22 | document.addEventListener(eventName, trigger, options); 23 | return () => document.removeEventListener(eventName, trigger, options); 24 | }, 25 | [eventName, options] 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /packages/script/docs/useScriptSuspense.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useScriptSuspense 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Script 8 | path: /script 9 | order: 3 10 | --- 11 | 12 | # useScriptSuspense 13 | 14 | The suspense version of `useScript`, it returns a single `boolean` value indicating whether script loads successfully. 15 | 16 | ```typescript 17 | function useScriptSuspense(src?: string): boolean 18 | ``` 19 | 20 | A component using `useScriptSuspense` must reside inside a ``. 21 | 22 | ```javascript 23 | import {useScriptSuspense} from '@huse/script'; 24 | 25 | const Demo = () => { 26 | const success = useScriptSuspense('/sdk.js'); 27 | 28 | return ( 29 |
30 | {sdk.uid()} 31 |
32 | ); 33 | }; 34 | 35 | const App = () => ( 36 | Loading ...
}> 37 | 38 | 39 | ); 40 | ``` -------------------------------------------------------------------------------- /packages/intended-lazy/docs/useIntendedLazyCallback.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useIntendedLazyCallback 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Intended Lazy 8 | path: /intended-lazy 9 | order: 1 10 | --- 11 | 12 | # useIntendedLazyCallback 13 | 14 | Like [`useIntendedLazyValue`](./use-intended-lazy-value) but accept a callback to make it reference stable. 15 | 16 | **Note: The returned callback is UNSAFE to call inside `render` and `useLayoutEffect` as well as its children's corresponding lifecycles.** 17 | 18 | ```typescript 19 | function useIntendedLazyCallback(fn: T): T 20 | ``` 21 | 22 | By using `useIntendedLazyCallback` we are able to implement a trick called "performance bridge", that is, a bridge component "swallows" frequetly changing props and make it reference stable in order to make the actual component memoizeble. 23 | 24 | 25 | -------------------------------------------------------------------------------- /packages/scroll-lock/docs/demo/useScrollLock.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import {Button, Modal} from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import {useScrollLock} from '@huse/scroll-lock'; 5 | 6 | export default () => { 7 | const [modalOpen, setModalOpen] = useState(false); 8 | useScrollLock(modalOpen); 9 | return ( 10 | <> 11 | 12 | { 13 | modalOpen && ( 14 | setModalOpen(false)} 18 | onCancel={() => setModalOpen(false)} 19 | > 20 | This is a modal 21 | 22 | ) 23 | } 24 | 25 | ); 26 | }; -------------------------------------------------------------------------------- /packages/scroll-lock/src/index.ts: -------------------------------------------------------------------------------- 1 | import {useEffect, useRef} from 'react'; 2 | 3 | export function useScrollLock(lock: boolean): void { 4 | const previousOverflowRef = useRef(''); 5 | useEffect( 6 | () => { 7 | if (!lock) { 8 | return; 9 | } 10 | 11 | /* istanbul ignore next */ 12 | if (typeof document === 'undefined') { 13 | return; 14 | } 15 | 16 | previousOverflowRef.current = document.body.style.overflow; 17 | document.body.style.overflow = 'hidden'; 18 | 19 | return () => { 20 | // Do not reset if other scripts modify style. 21 | if (document.body.style.overflow === 'hidden') { 22 | document.body.style.overflow = previousOverflowRef.current; 23 | } 24 | }; 25 | }, 26 | [lock] 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /packages/action-pending/src/__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | import {renderHook, act} from '@testing-library/react-hooks'; 2 | import {useActionPending} from '../index'; 3 | 4 | test('return async function', () => { 5 | const {result} = renderHook(() => useActionPending(x => Promise.resolve(x + 1))); 6 | expect(typeof result.current[0]).toBe('function'); 7 | expect(typeof result.current[1]).toBe('number'); 8 | }); 9 | 10 | test('correct resolve', async () => { 11 | const {result} = renderHook(() => useActionPending(x => Promise.resolve(x + 1))); 12 | let value = 0; 13 | await act(() => result.current[0](1).then(v => (value = v))); 14 | expect(value).toBe(2); 15 | }); 16 | 17 | test('decrement pending on finish', async () => { 18 | const {result} = renderHook(() => useActionPending(x => Promise.resolve(x + 1))); 19 | await act(() => result.current[0](1)); 20 | expect(result.current[1]).toBe(0); 21 | }); 22 | 23 | 24 | -------------------------------------------------------------------------------- /packages/document-title/src/__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-no-bind */ 2 | import {useState} from 'react'; 3 | import {render, fireEvent} from '@testing-library/react'; 4 | import {useDocumentTitle} from '../index'; 5 | 6 | const Foo = ({titleX, titleY}) => { 7 | const [useY, setUseY] = useState(false); 8 | useDocumentTitle(useY ? titleY : titleX); 9 | return setUseY(v => !v)} />; 10 | }; 11 | 12 | test('initial title', () => { 13 | render(); 14 | expect(document.title).toBe('foo'); 15 | }); 16 | 17 | test('change title', () => { 18 | const {container} = render(); 19 | fireEvent.click(container.querySelector('#switch')); 20 | expect(document.title).toBe('bar'); 21 | }); 22 | 23 | test('revert on unmount', () => { 24 | const {unmount} = render(); 25 | unmount(); 26 | expect(document.title).toBe(''); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/snapshot/docs/demo/useSnapshotState2.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Button, Input} from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import {useSnapshotState} from '@huse/snapshot'; 5 | 6 | export default () => { 7 | // save to history after 2s 8 | const [content, setContent, {undo, redo, canUndo, canRedo}] = useSnapshotState('', {delay: 2000}); 9 | return ( 10 | <> 11 |
12 |
13 | 14 | 15 |
16 |
17 | setContent(e.target.value)} /> 18 |
19 |
20 | 21 | ); 22 | }; -------------------------------------------------------------------------------- /packages/intended-lazy/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [1.1.2](https://github.com/ecomfe/react-hooks/compare/@huse/intended-lazy@1.1.1...@huse/intended-lazy@1.1.2) (2021-07-22) 7 | 8 | **Note:** Version bump only for package @huse/intended-lazy 9 | 10 | 11 | 12 | 13 | 14 | ## [1.1.1](https://github.com/ecomfe/react-hooks/compare/@huse/intended-lazy@1.1.0...@huse/intended-lazy@1.1.1) (2021-05-21) 15 | 16 | 17 | ### Bug Fixes 18 | 19 | * fix messy nav bar ([#64](https://github.com/ecomfe/react-hooks/issues/64)) ([d300f58](https://github.com/ecomfe/react-hooks/commit/d300f5800310f880d79e36b459c502c5b4f5cfe2)) 20 | 21 | 22 | 23 | 24 | 25 | # 1.1.0 (2020-11-15) 26 | 27 | 28 | ### Features 29 | 30 | * intended lazy hooks ([f76d6fa](https://github.com/ecomfe/react-hooks/commit/f76d6faa6a6a0f37dfcf0b7a8c99f40e6a5925de)) 31 | -------------------------------------------------------------------------------- /packages/document-event/docs/demo/useDocumentEvent.tsx: -------------------------------------------------------------------------------- 1 | import React, { useReducer } from 'react'; 2 | import { useDocumentEvent } from '@huse/document-event'; 3 | 4 | export default () => { 5 | const [down, addDown] = useReducer(v => v + 1, 0); 6 | const [up, addUp] = useReducer(v => v + 1, 0); 7 | const [key, addKey] = useReducer(v => v + 1, 0); 8 | useDocumentEvent('mousedown', addDown); 9 | useDocumentEvent('mouseup', addUp); 10 | useDocumentEvent('keypress', addKey); 11 | return ( 12 | <> 13 |

14 |

Mouse Down:

15 | {down} times 16 |

17 |

18 |

Mouse Up:

19 | {up} times 20 |

21 |

22 |

Key Press:

23 | {key} times 24 |

25 | 26 | ); 27 | }; -------------------------------------------------------------------------------- /packages/immer/docs/demo/useImmerState.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Button} from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import {useImmerState} from '@huse/immer'; 5 | 6 | export default () => { 7 | const [state, setState] = useImmerState({value: 1}); 8 | return ( 9 | <> 10 |

current value: {state.value}

11 |
12 | {/* mutate state */} 13 | 14 | {/* return a new state */} 15 | 16 | {/* set to a new state */} 17 | 18 |
19 | 20 | ); 21 | }; -------------------------------------------------------------------------------- /packages/transition-state/docs/demo/useTransitionState.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback} from 'react'; 2 | import {Button} from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import {useTransitionState} from '@huse/transition-state'; 5 | 6 | export default () => { 7 | // Save data to server, mocked 8 | const saveData = () => new Promise(resolve => setTimeout(resolve, 20)); 9 | const [message, setMessage] = useTransitionState('', 4 * 1000); 10 | const submit = useCallback( 11 | () => { 12 | // Will change back to "Submit" after 4 seconds 13 | saveData().then(() => setMessage('Saved!')); 14 | }, 15 | [setMessage, saveData] 16 | ); 17 | return ( 18 | <> 19 | 20 | 21 | {message} 22 | 23 | 24 | ); 25 | }; -------------------------------------------------------------------------------- /packages/action-pending/docs/demo/useActionPending.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button, Spin } from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import './useActionPending.less'; 5 | import { useActionPending } from '@huse/action-pending'; 6 | 7 | export default () => { 8 | const wait = time => new Promise(resolve => setTimeout(resolve, time)); 9 | const [waitTime, pendingCount] = useActionPending(wait); 10 | return ( 11 | <> 12 |
13 | 14 | 15 | 16 | 17 |
18 | {!!pendingCount &&
{pendingCount} timeouts in the queue
} 19 | 20 | ); 21 | }; -------------------------------------------------------------------------------- /packages/number/src/index.ts: -------------------------------------------------------------------------------- 1 | import {useMethodsNative} from '@huse/methods'; 2 | 3 | export interface CounterOptions { 4 | min?: number; 5 | max?: number; 6 | step?: number; 7 | } 8 | 9 | export function useCounter(initialValue: number = 0, options: CounterOptions = {}) { 10 | const {min = -Infinity, max = Infinity, step = 1} = options; 11 | return useMethodsNative( 12 | { 13 | increment(state: number) { 14 | return Math.min(max, state + step); 15 | }, 16 | decrement(state: number) { 17 | return Math.max(min, state - step); 18 | }, 19 | inc(state: number) { 20 | return Math.min(max, state + step); 21 | }, 22 | dec(state: number) { 23 | return Math.max(min, state - step); 24 | }, 25 | reset(state: number, value: number = 0) { 26 | return value; 27 | }, 28 | }, 29 | initialValue 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /packages/optimistic/src/interface.ts: -------------------------------------------------------------------------------- 1 | /* istanbul ignore file */ 2 | export type NextState = (state: T) => T; 3 | 4 | export type AsyncWorkFlow = () => Generator | Promise, void, R>; 5 | 6 | export interface ReducerEntry { 7 | readonly transactionID?: number; 8 | readonly next: NextState; 9 | } 10 | 11 | export interface OptimisticState { 12 | readonly optimistic: boolean; 13 | // eslint-disable-next-line @typescript-eslint/ban-types 14 | readonly archive: T | {}; 15 | readonly queue: Array>; 16 | readonly hostState: T; 17 | } 18 | 19 | export type Execute = (next: NextState>) => void; 20 | 21 | export type ReduceHint = NextState | [AsyncWorkFlow, NextState]; 22 | 23 | export type Factory = (payload: P) => ReduceHint; 24 | 25 | export interface SetState { 26 | (input: T | NextState): void; 27 | (setAsync: Promise | Promise>, setSync: T | NextState): void; 28 | } 29 | -------------------------------------------------------------------------------- /packages/router/src/url.ts: -------------------------------------------------------------------------------- 1 | const normalize = (pathname: string): string => pathname.replace(/\/{2,}/g, '/'); 2 | const trimTrailingSlashes = (pathname: string): string => pathname.replace(/\/+$/, ''); 3 | 4 | export const resolve = (from: string, to: string): string => { 5 | if (!to) { 6 | return normalize(trimTrailingSlashes(from)); 7 | } 8 | 9 | if (to.startsWith('/')) { 10 | return to; 11 | } 12 | 13 | const segments = normalize(trimTrailingSlashes(from)).split('/'); 14 | const toSegments = normalize(trimTrailingSlashes(to)).split('/'); 15 | 16 | for (const segment of toSegments) { 17 | if (segment === '..') { 18 | // Reserve the heading `""` segment in order to currectly join them including a leading `"/""` 19 | segments.length > 1 && segments.pop(); 20 | } 21 | else if (segment !== '.') { 22 | segments.push(segment); 23 | } 24 | } 25 | 26 | return segments.length > 1 ? normalize(segments.join('/')) : '/'; 27 | }; 28 | -------------------------------------------------------------------------------- /packages/web-socket/src/interface.ts: -------------------------------------------------------------------------------- 1 | import {ReadyState} from './constants'; 2 | 3 | export interface Options { 4 | onOpen?: (event: WebSocketEventMap['open']) => void; 5 | onClose?: (event: WebSocketEventMap['close']) => void; 6 | onMessage?: (event: WebSocketEventMap['message']) => void; 7 | onError?: (event: WebSocketEventMap['error']) => void; 8 | filter?: (message: WebSocketEventMap['message']) => boolean; 9 | reconnectOnClose?: (event: WebSocketEventMap['close']) => boolean; 10 | reconnectOnError?: (event: WebSocketEventMap['error']) => boolean; 11 | reconnectInterval?: number; 12 | reconnectAttempts?: number; 13 | autoStart?: boolean; 14 | } 15 | 16 | export type MessageType = string | ArrayBuffer | SharedArrayBuffer | Blob | ArrayBufferView; 17 | 18 | export type SendMessage = (message: MessageType) => void; 19 | 20 | export type StartWebSocket = () => void; 21 | 22 | export type CloseWebSocket = () => void; 23 | 24 | export interface ReadyStateState { 25 | [url: string]: ReadyState; 26 | } 27 | -------------------------------------------------------------------------------- /packages/previous-value/docs/demo/useOriginalCopy.tsx: -------------------------------------------------------------------------------- 1 | import React, { useReducer, useEffect } from 'react'; 2 | import { Button } from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import { useOriginalCopy } from '@huse/previous-value'; 5 | 6 | export default () => { 7 | const [effectsCount, runEffect] = useReducer(v => v + 1, 0); 8 | const forceUpdate = useReducer(v => v + 1, 0)[1]; 9 | // This is not memoized 10 | const value = {x: 1}; 11 | // The original copy of value if retrieved on each render 12 | const originalValue = useOriginalCopy(value); 13 | // originalValue will be reference equal on different render, effect runs only once 14 | useEffect( 15 | () => { 16 | runEffect(); 17 | }, 18 | [originalValue] 19 | ); 20 | console.log(effectsCount); 21 | return ( 22 |
23 |

Effect run {effectsCount} times.

24 | 25 |
26 | ); 27 | }; -------------------------------------------------------------------------------- /packages/request/docs/demo/useRequest.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState, useCallback, useEffect, useRef} from 'react'; 2 | import {Spin, Button, Slider, notification} from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import {useRequest} from '@huse/request'; 5 | 6 | export default () => { 7 | const [base, setBase] = useState(0); 8 | const generateRandom = useCallback( 9 | // Delay for 1.5s and resolve with a random value 10 | ({base}) => new Promise(resolve => setTimeout(() => resolve(base + Math.random()), 1500)), 11 | [] 12 | ); 13 | const result = useRequest(generateRandom, {base}); 14 | return ( 15 | <> 16 |
17 | Base Value: 18 |
19 |
20 | {result.pending 21 | ? 22 | :

Generated Value: {result.data}

23 | } 24 |
25 | 26 | ); 27 | }; -------------------------------------------------------------------------------- /packages/input-value/src/__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | import {renderHook, act} from '@testing-library/react-hooks'; 2 | import {useInputValue} from '../index'; 3 | 4 | test('has value and onChange', () => { 5 | const {result} = renderHook(() => useInputValue()); 6 | expect(typeof result.current.value).toBe('string'); 7 | expect(typeof result.current.onChange).toBe('function'); 8 | }); 9 | 10 | test('default initial value to empty string', () => { 11 | const {result} = renderHook(() => useInputValue()); 12 | expect(result.current.value).toBe(''); 13 | }); 14 | 15 | test('specified initial value', () => { 16 | const initialValue = 'foo'; 17 | const {result} = renderHook(() => useInputValue(initialValue)); 18 | expect(result.current.value).toBe(initialValue); 19 | }); 20 | 21 | test('change value via onChange', () => { 22 | const {result} = renderHook(() => useInputValue()); 23 | const newValue = 'bar'; 24 | act(() => result.current.onChange({target: {value: newValue}})); 25 | expect(result.current.value).toBe(newValue); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/number/docs/useCounter.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useCounter 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Number 8 | path: /number 9 | order: 2 10 | --- 11 | 12 | # useCounter 13 | 14 | Manages number as a counter which can increment and decrement. 15 | 16 | ```typescript 17 | interface CounterOptions { 18 | min?: number; // Min value of counter 19 | max?: number; // Max value of counter 20 | step?: number; // Step on each increment or decrement call, default to 1 21 | } 22 | 23 | function useCounter(initialValue: number, options?: CounterOptions): CounterMethods; 24 | ``` 25 | 26 | This is a wrap of `useMethods` in `@huse/methods` and contains methods below: 27 | 28 | ```typescript 29 | { 30 | increment(): void; // plus step 31 | inc(): void; 32 | decrement(): void; // subtract step 33 | dec(): void; 34 | reset(value?: number): void; // reset to value, defaults to 0 35 | } 36 | ``` 37 | 38 | `inc` is an alias to `increment` and `dec` is an alias to `decrement`. 39 | 40 | 41 | -------------------------------------------------------------------------------- /packages/hover/docs/useHover.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useHover 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Hover 8 | path: /hover 9 | order: 2 10 | --- 11 | 12 | # useHover 13 | 14 | This hook returns a set of props to handle mouse events in order to report whether element is currently in hover state. 15 | 16 | ```typescript 17 | interface HoverOptions { 18 | delay?: number; 19 | } 20 | 21 | interface HoverCallbacks { 22 | onMouseEnter(event: MouseEvent): void; 23 | onMouseLeave(event: MouseEvent): void; 24 | } 25 | 26 | function useHover(options: HoverOptions = {}): [boolean, HoverCallbacks] 27 | ``` 28 | 29 | The `delay` option is default to `0`, with a value less than or equals to 0 hover state will change immediately on mouse enter and leave. 30 | 31 | 32 | 33 | Note that when `delay` is a positive value, `onEnter` and `onLeave` callbacks are also debounced, 34 | once you want these callbacks to execute immediately without debouncing, compose callbacks yourself: 35 | 36 | 37 | -------------------------------------------------------------------------------- /scripts/generate/prompt.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | 3 | const questions = [ 4 | { 5 | type: 'input', 6 | name: 'packageName', 7 | message: 'package name:', 8 | transformer(name) { 9 | return `@huse/${name}`; 10 | }, 11 | validate(name) { 12 | return name.length > 1; 13 | }, 14 | }, 15 | { 16 | type: 'confirm', 17 | name: 'hookTest', 18 | message: 'test with hook support:', 19 | }, 20 | { 21 | type: 'confirm', 22 | name: 'domTest', 23 | message: 'test with DOM support:', 24 | }, 25 | { 26 | type: 'confirm', 27 | name: 'demo', 28 | message: 'has demo:', 29 | default: false, 30 | }, 31 | { 32 | type: 'input', 33 | name: 'hookName', 34 | message: 'initial hook name:', 35 | validate(name) { 36 | return name.length > 3 && name.startsWith('use'); 37 | }, 38 | }, 39 | ]; 40 | 41 | module.exports = () => inquirer.prompt(questions); 42 | -------------------------------------------------------------------------------- /packages/poll/docs/demo/usePoll2.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback} from 'react'; 2 | import {Spin} from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import {usePoll} from '@huse/poll'; 5 | 6 | export default () => { 7 | const generateRandom = useCallback( 8 | // Delay for 1.5s and resolve with a random value 9 | () => new Promise(resolve => setTimeout(() => resolve(Math.random()), 1500)), 10 | [] 11 | ); 12 | const options = { 13 | minInterval: 5 * 1000, 14 | maxInterval: 20 * 1000, 15 | maxIdleTime: 30 * 1000, 16 | stopOnInactive: true, 17 | }; 18 | const [value, pendingCount] = usePoll(generateRandom, options); 19 | return ( 20 | <> 21 |
22 | {value} 23 | {!!pendingCount && } 24 |
25 |

Poll will slow down when you keep inactive for 30s.

26 |

Poll will stop when you switch to another app.

27 | 28 | ); 29 | }; -------------------------------------------------------------------------------- /packages/update/docs/useForceUpdate.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useForceUpdate 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Update 8 | path: /update 9 | order: 2 10 | --- 11 | 12 | # useForceUpdate 13 | 14 | Return a function which when invoked will update current component. 15 | 16 | ```typescript 17 | function useForceUpdate(): () => void; 18 | ``` 19 | 20 | This could have a combination with `useRef` to implement some interesting staff such as a `useState`: 21 | 22 | ```javascript 23 | import {useForceUpdate} from '@huse/update'; 24 | 25 | const useRefState = initialValue => { 26 | const state = useRef(initialValue); 27 | const forceUpdate = useForceUpdate(); 28 | const setState = useCallback( 29 | value => { 30 | state.current = value; 31 | forceUpdate(); 32 | }, 33 | [forceUpdate] 34 | ); 35 | return [state.current, setState]; 36 | }; 37 | ``` 38 | 39 | Every time `forceUpdate` is invoked, component will have a re-render despite of other state changes or `memo` usage. 40 | 41 | 42 | -------------------------------------------------------------------------------- /packages/scroll-position/docs/useScrollPosition.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useScrollPosition 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Scroll Position 8 | path: /scroll-position 9 | order: 2 10 | --- 11 | 12 | # useScrollPosition 13 | 14 | Observe and return scroll position of an element or window. 15 | 16 | ```typescript 17 | interface ScrollPosition { 18 | x: number; 19 | y: number; 20 | left: number; 21 | top: number; 22 | scrollLeft: number; 23 | scrollTop: number; 24 | } 25 | 26 | function useScrollPosition(element?: HTMLElement | null): ScrollPosition 27 | ``` 28 | 29 | **Note there are different behaviors when `element` is either `null` or `undefined`, commonly `useRef`'s initial value is `null`.** 30 | When `documentElement` is the observed target, simple `useScrollPosition()` without argument. 31 | 32 | In order to satisfy different developers, the returned `ScrollPosition` has a set of different key pairs, 33 | all `x`, `left`, `scrollLeft` have same value while `y`, `top`, `scrollTop` have same value. 34 | 35 | 36 | -------------------------------------------------------------------------------- /packages/action-pending/src/index.ts: -------------------------------------------------------------------------------- 1 | import {useCallback, useEffect, useRef} from 'react'; 2 | import {useCounter} from '@huse/number'; 3 | 4 | type AsyncFunction = (...args: any[]) => Promise; 5 | 6 | export function useActionPending
(action: A): [A, number] { 7 | const unmounted = useRef(false); 8 | const [pendingCount, {inc, dec}] = useCounter(); 9 | const actionWithPending = useCallback( 10 | (...args: Parameters) => { 11 | inc(); 12 | const pending = action(...args); 13 | const safeDec = () => { 14 | if (!unmounted.current) { 15 | dec(); 16 | } 17 | }; 18 | pending.then(safeDec, safeDec); 19 | return pending; 20 | }, 21 | [action, dec, inc] 22 | ); 23 | useEffect( 24 | () => { 25 | unmounted.current = false; 26 | return () => { 27 | unmounted.current = true; 28 | }; 29 | }, 30 | [] 31 | ); 32 | return [actionWithPending as A, pendingCount]; 33 | } 34 | -------------------------------------------------------------------------------- /packages/timeout/docs/demo/useTimeout.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import {Slider} from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import {useTimeout, useInterval} from '@huse/timeout'; 5 | 6 | export default () => { 7 | const [theme, setTheme] = useState('light'); 8 | const [delay, setDelay] = useState(2); 9 | console.log(delay); 10 | useTimeout( 11 | () => { 12 | console.log('timeout'); 13 | setTheme(theme => (theme === 'light' ? 'dark' : 'light')); 14 | }, 15 | delay * 1000 16 | ); 17 | const style = { 18 | height: 40, 19 | lineHeight: '40px', 20 | textAlign: 'center', 21 | backgroundColor: theme === 'light' ? '#fff' : '#222', 22 | color: theme === 'light' ? '#666' : '#f4f5f6', 23 | }; 24 | return ( 25 | <> 26 |
27 | Choose a delay to switch theme: 28 | 29 |
30 |
31 | Hello World 32 |
33 | 34 | ); 35 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Baidu EFE team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/performance/docs/usePerformanceTiming.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: usePerformanceTiming 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Performance 8 | path: /performance 9 | order: 2 10 | --- 11 | 12 | # usePerformanceTiming 13 | 14 | This hook tracks component's layout times and report it to a custom callback function. 15 | 16 | ```typescript 17 | interface Timings { 18 | [flag: string]: number; 19 | initialRender: number; 20 | initialLayout: number; 21 | } 22 | 23 | interface TimingOptions { 24 | flags?: {[name: string]: boolean}; 25 | } 26 | 27 | function usePerformanceTiming(callback: (timings: Timings) => void, options?: TimingOptions): void 28 | ``` 29 | 30 | It will always trigger `callback` on the first layout, consequent triggers depend on `flags` change, 31 | every time when a flag is changed from `false` to `true` the `callback` will be triggered. 32 | 33 | `callback` function receives a `Timings` object containing at least `initialRender` and `initialLayout` properties, 34 | all flags evaluated to `true` also reflects a property in this argument. 35 | 36 | -------------------------------------------------------------------------------- /packages/transition-state/src/index.ts: -------------------------------------------------------------------------------- 1 | import {useState, useRef, useEffect, useCallback} from 'react'; 2 | 3 | export type TransitionStateHook = [ 4 | S, 5 | (value: S, duration?: number) => void, 6 | ]; 7 | 8 | export function useTransitionState(defaultValue: S, defaultDuration: number = -1): TransitionStateHook { 9 | const [value, setValue] = useState(defaultValue); 10 | const durationRef = useRef(defaultDuration); 11 | useEffect( 12 | () => { 13 | if (value !== defaultValue && durationRef.current >= 0) { 14 | const tick = setTimeout( 15 | () => setValue(defaultValue), 16 | durationRef.current 17 | ); 18 | 19 | return () => clearTimeout(tick); 20 | } 21 | }, 22 | [defaultValue, value] 23 | ); 24 | const setTransition = useCallback( 25 | (value: S, duration: number = defaultDuration) => { 26 | durationRef.current = duration; 27 | setValue(value); 28 | }, 29 | [defaultDuration] 30 | ); 31 | return [value, setTransition]; 32 | } 33 | -------------------------------------------------------------------------------- /packages/merged-ref/docs/demo/useMergedRef.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState, useCallback, useRef, useEffect} from 'react'; 2 | import {useMergedRef} from '@huse/merged-ref'; 3 | 4 | export default () => { 5 | const [size, setSize] = useState([0, 0]); 6 | // callback ref 7 | const observeSize = useCallback( 8 | element => element && setSize([element.offsetWidth, element.offsetHeight]), 9 | [] 10 | ); 11 | // mutation ref 12 | const elementRef = useRef(null); 13 | useEffect( 14 | () => { 15 | if (elementRef.current) { 16 | elementRef.current.animate( 17 | [{opacity: '1'}, {opacity: '.4'}, {opacity: '1'}], 18 | {duration: 800, iterations: 4} 19 | ); 20 | } 21 | }, 22 | [] 23 | ); 24 | const ref = useMergedRef([observeSize, elementRef]); 25 | return ( 26 |
30 | {size[0]} x {size[1]} 31 |
32 | ); 33 | }; -------------------------------------------------------------------------------- /packages/script/demo/entries/index.js: -------------------------------------------------------------------------------- 1 | import {useState, useEffect, Suspense} from 'react'; 2 | import {render} from 'react-dom'; 3 | import SimpleScript from '@/components/SimpleScript'; 4 | import WithSuspense from '@/components/WithSuspense'; 5 | 6 | const App = () => { 7 | const [json, setJSON] = useState(''); 8 | useEffect( 9 | () => { 10 | const tick = setInterval( 11 | () => setJSON(JSON.stringify(window.dynamicScripts, null, ' ')), 12 | 500 13 | ); 14 | return () => clearInterval(tick); 15 | }, 16 | [] 17 | ); 18 | 19 | return ( 20 |
21 |

Simple Use Script

22 | 23 |

Use With Suspense

24 | Currently Loading...

}> 25 | 26 |
27 |

Content Loaded:

28 |
29 |                 {json}
30 |             
31 |
32 | ); 33 | }; 34 | 35 | render( 36 | , 37 | document.body.appendChild(document.createElement('div')) 38 | ); 39 | -------------------------------------------------------------------------------- /docs/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "huse - React Hooks Library" 3 | hero: 4 | title: "huse" 5 | desc: 这是收集了常用的react hooks,用于支持百度内部的开发,我们欢迎社区所有人参与共建 6 | actions: 7 | - text: 快速上手 8 | link: /zh-CN/docs/getting-started 9 | features: 10 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/813f5ed9-6bc4-43d4-9f74-ec81ecf35733/k7htg6n4_w144_h144.png 11 | title: 内容丰富 12 | desc: 拥有丰富的自定义 Hooks,每个 Hooks 都有丰富的在线示例供您体验。 13 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/7659205c-6637-4fa2-8529-d32e5818304b/k7htflfb_w144_h144.png 14 | title: 完备的教程 15 | desc: 适合全年龄段的丰富的 React Hooks 教程,想学 React Hooks,来这里就够了。 16 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/6319a122-e8b8-497f-9b45-37cfbe77edaa/k7htfx7t_w144_h144.png 17 | title: 生产可用 18 | desc: 已经过百度大量的线上系统的考验及打磨,健壮性值得信赖。 19 | footer: Open-source MIT Licensed | Copyright © 2020
Powered by [Baidu EFE team](https://ecomfe.github.io/) 20 | --- 21 | 22 | ## 轻松上手 23 | 24 | 安装依赖: 25 | 26 | ```bash 27 | npm install huse --save 28 | ``` 29 | 30 | 也可以使用 `yarn`: 31 | 32 | ```bash 33 | yarn add huse 34 | ``` 35 | 36 | 使用 Hooks 37 | 38 | ```bash 39 | import { useBoolean } from 'huse'; 40 | ``` 41 | -------------------------------------------------------------------------------- /packages/collection/docs/useArray.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useArray 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Collection 8 | path: /collection 9 | order: 2 10 | --- 11 | 12 | # useArray 13 | 14 | Encapsulate arrays into methods via `useMethods`, contains methods below: 15 | 16 | ```typescript 17 | { 18 | push(item: T): void; 19 | unshift(item: T): void; 20 | pop(): void; 21 | shift(): void; 22 | slice(start?: number, end?: number): void; 23 | splice(index: number, deleteCount: number, ...insertions: T[]): void; 24 | remove(item: T): void; 25 | removeAt(index: number): void; 26 | insertAt(index: number, item: T): void; 27 | concat(item: T | T[]): void; 28 | replace(from: T, to: T): void; 29 | replaceAll(from: T, to: T): void; 30 | replaceAt(index: number, item: T): void; 31 | filter(predicate: (item: T, index: number) => boolean): void; 32 | union(array: T[]): void; 33 | intersect(array: T[]): void; 34 | difference(array: T[]): void; 35 | reverse(): void; 36 | sort(compare?: (x: T, y: T) => number): void; 37 | clear(): void; 38 | } 39 | ``` 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /packages/optimistic/docs/useOptimisticTask.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useOptimisticTask 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Optimistic 8 | path: /optimistic 9 | order: 4 10 | --- 11 | 12 | # useOptimisticTask 13 | 14 | This is a binding of `useOptimisticState` and an async task. 15 | 16 | ```typescript 17 | function useOptimisticTask(task: (arg: A) => Promise, optimisticTask: (arg: A) => S, initialState: S); 18 | ``` 19 | 20 | - The `task` is an async function `(arg: TArg) => Promise`. 21 | - The `optimisticTask` is a sync version of task provides an optimistic response `(arg: TArg) => TState`. 22 | - Returned `run` function receives the same argument as `task`. 23 | 24 | ```js 25 | const newTodo = async todo => { 26 | const newTodo = await saveTodo(todo); 27 | // We recommend to use a reducer since it is asynchronous 28 | return todos => [...todos, {...newTodo, pending: false, deleted: false}]; 29 | }; 30 | const optimisticNewTodo = todo => todos => [...todos, {...todo, pending: true, deleted: false}]; 31 | const [todos, addTodo] = useOptimisticTask(newTodo, optimisticNewTodo, []); 32 | ``` 33 | 34 | `useOptimisticTask` is useful when encapsulating business aware hooks. -------------------------------------------------------------------------------- /packages/user-media/docs/useUserMedia.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useUserMedia 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: User Media 8 | path: /user-media 9 | order: 2 10 | --- 11 | 12 | # useUserMedia 13 | 14 | This hook tries to open a media stream via its `constraints` argument, returning a context indicating current streaming state, see [getUserMedia](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia) to understand its underlying browser capabilities. 15 | 16 | ```typescript 17 | interface UserMediaHook { 18 | stream: MediaStream | null; 19 | recording: boolean; 20 | error: Error | null; 21 | start: () => void; 22 | stop: () => void; 23 | } 24 | 25 | function useUserMedia( 26 | constraints?: MediaStreamConstraints, 27 | onSuccess?: (stream: MediaStream) => void, 28 | onError?: (error: Error) => void 29 | ): UserMediaHook; 30 | ``` 31 | 32 | By default `useUserMedia` requires both video and audio channels. 33 | 34 | **NOTE: On a browser where `getUserMedia` is not implemented, this hook returns `UserMediaHook` object with a special `error` containing `code` property of `"ERR_METHOD_NOT_IMPLEMENTED"`.** 35 | 36 | 37 | -------------------------------------------------------------------------------- /packages/scroll-into-view/docs/useScrollIntoView.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useScrollIntoView 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Scroll Into View 8 | path: /scroll-into-view 9 | order: 2 10 | --- 11 | 12 | # useScrollIntoView 13 | 14 | To make an active element scroll into view on its mount. 15 | 16 | ```typescript 17 | function useScrollIntoView( 18 | ref: RefObject, 19 | active: boolean = true, 20 | options: boolean | ScrollIntoViewOptions = {behavior: 'smooth'} 21 | ): void 22 | ``` 23 | 24 | This hook conforms to `scrollIntoView` function on `HTMLElement`, the `active` argument controls whether scroll should be performed. 25 | 26 | It is recommended to have only one active element performing scroll. 27 | 28 | ```javascript 29 | const App = () => { 30 | const [activeIndex, setActiveIndex] = useState(0); 31 | 32 | return ( 33 |
34 |
35 | {colors.map((c, i) => setActiveIndex(i)} />)} 36 |
37 | {colors.map((c, i) => )} 38 |
39 | ); 40 | }; 41 | ``` 42 | -------------------------------------------------------------------------------- /packages/snapshot/docs/useSnapshotState.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useSnapshotState 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Snapshot 8 | path: /snapshot 9 | order: 2 10 | --- 11 | 12 | # useSnapshotState 13 | 14 | Creates a state with version history and provides `undo` and `redo` functions to traverse the history. 15 | 16 | ```typescript 17 | export interface SnapshotOptions { 18 | // debounce time to commit value to history, defaults to no debounce 19 | delay?: number; 20 | // max history length, defaults to Infinity 21 | limit?: number; 22 | } 23 | 24 | interface Snapshot { 25 | backLength: number; 26 | forwardLength: number; 27 | canUndo: boolean; 28 | canRedo: boolean; 29 | undo(): void; 30 | redo(): void; 31 | } 32 | 33 | export type SnapshotHook = [T, Dispatch>, Snapshot]; 34 | 35 | function useSnapshotState(init: T | (() => T), options: SnapshotOptions = {}): SnapshotHook 36 | ``` 37 | 38 | 39 | 40 | By passing a `delay` option `useSnapshotState` will behave as debounced, 41 | that is only commit value to history when it is not changed after a certain time. 42 | 43 | 44 | -------------------------------------------------------------------------------- /packages/scroll-lock/src/__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | import {renderHook} from '@testing-library/react-hooks'; 2 | import {useScrollLock} from '../index'; 3 | 4 | test('scroll lock', () => { 5 | renderHook(() => useScrollLock(true)); 6 | expect(document.body.style.overflow).toBe('hidden'); 7 | }); 8 | 9 | test('no scroll lock', () => { 10 | renderHook(() => useScrollLock(false)); 11 | expect(document.body.style.overflow).toBe(''); 12 | }); 13 | 14 | test('free lock on unmount', () => { 15 | const {unmount} = renderHook(() => useScrollLock(false)); 16 | unmount(); 17 | expect(document.body.style.overflow).toBe(''); 18 | }); 19 | 20 | test('return previous overflow', () => { 21 | document.body.style.overflow = 'visible'; 22 | const {unmount} = renderHook(() => useScrollLock(false)); 23 | unmount(); 24 | expect(document.body.style.overflow).toBe('visible'); 25 | document.body.style.overflow = ''; 26 | }); 27 | 28 | test('no revert when overflow is modified', () => { 29 | const {unmount} = renderHook(() => useScrollLock(false)); 30 | document.body.style.overflow = 'visible'; 31 | unmount(); 32 | expect(document.body.style.overflow).toBe('visible'); 33 | document.body.style.overflow = ''; 34 | }); 35 | -------------------------------------------------------------------------------- /packages/optimistic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@huse/optimistic", 3 | "version": "0.10.1", 4 | "keywords": [ 5 | "react", 6 | "hooks" 7 | ], 8 | "homepage": "https://github.com/ecomfe/react-hooks/tree/master/packages/optimistic", 9 | "bugs": { 10 | "url": "https://github.com/ecomfe/react-hooks/issues" 11 | }, 12 | "license": "MIT", 13 | "main": "cjs/index.js", 14 | "module": "es/index.js", 15 | "types": "es/index.d.ts", 16 | "files": [ 17 | "cjs", 18 | "es", 19 | "src" 20 | ], 21 | "scripts": { 22 | "build": "rm -rf es cjs && tsc & tsc --module ESNext --outDir ./es", 23 | "build-check": "tsc", 24 | "lint": "skr lint --strict src demo", 25 | "test": "skr test --coverage" 26 | }, 27 | "devDependencies": { 28 | "@reskript/cli": "^1.10.1", 29 | "@reskript/cli-lint": "^1.10.1", 30 | "@reskript/cli-test": "^1.10.1", 31 | "@reskript/config-lint": "^1.10.1", 32 | "@testing-library/react-hooks": "^7.0.1", 33 | "@types/react": "^17.0.14", 34 | "react": "^17.0.0", 35 | "typescript": "^4.3.5" 36 | }, 37 | "peerDependencies": { 38 | "react": ">=16.8.0" 39 | }, 40 | "publishConfig": { 41 | "access": "public", 42 | "registry": "https://registry.npmjs.com" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/derived-state/src/__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-no-bind */ 2 | import {renderHook, act} from '@testing-library/react-hooks'; 3 | import {useDerivedState} from '../index'; 4 | 5 | test('initial value', () => { 6 | const {result} = renderHook(() => useDerivedState(123)); 7 | expect(result.current[0]).toBe(123); 8 | expect(typeof result.current[1]).toBe('function'); 9 | }); 10 | 11 | test('set state', () => { 12 | const {result} = renderHook(() => useDerivedState(123)); 13 | act(() => result.current[1](456)); 14 | expect(result.current[0]).toBe(456); 15 | }); 16 | 17 | test('custom derive', () => { 18 | const derive = jest.fn(x => x + 1); 19 | const {result} = renderHook(() => useDerivedState(123, derive), {initialProps: {x: 123}}); 20 | expect(result.current[0]).toBe(124); 21 | expect(derive).toHaveBeenCalledWith(123, undefined); 22 | }); 23 | 24 | test('update derive', () => { 25 | const derive = jest.fn(x => x + 1); 26 | const {result, rerender} = renderHook(props => useDerivedState(props.x, derive), {initialProps: {x: 123}}); 27 | act(() => result.current[1](345)); 28 | rerender({x: 456}); 29 | expect(result.current[0]).toBe(457); 30 | expect(derive).toHaveBeenCalledWith(456, 345); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/effect-ref/docs/demo/useEffectRef.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback, createElement } from "react"; 2 | import { Select } from "antd"; 3 | import "antd/dist/antd.min.css"; 4 | import "./useEffectRef.less"; 5 | import { useEffectRef } from "@huse/effect-ref"; 6 | 7 | export default () => { 8 | const [tag, setTag] = useState("div"); 9 | const [message, setMessage] = useState(""); 10 | const updateMessage = useCallback( 11 | (element) => 12 | setMessage( 13 | `Root element is changed to <${element.nodeName.toLowerCase()}>` 14 | ), 15 | [] 16 | ); 17 | const ref = useEffectRef(updateMessage); 18 | return ( 19 |
20 | 26 | {createElement(tag, { ref },

{message}

)} 27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /packages/effect-ref/docs/useEffectRef.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useEffectRef 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Effect Ref 8 | path: /effect-ref 9 | order: 2 10 | --- 11 | 12 | # useEffectRef 13 | 14 | This hook returns a callback function, pass it as `ref` prop to any DOM element to run callback on element mount. 15 | 16 | Callback can return a clean-up function like `useEffect`'s callback to clean up side effects. 17 | 18 | A native callback ref may receive element argument as `null`, however `useEffectRef` handles this case internally, 19 | only `HTMLElement` node is passed to callback. 20 | 21 | ```typescript 22 | export type EffectRef = (element: E | null) => void; 23 | 24 | export type RefCallback = (element: E) => (() => void) | void; 25 | 26 | export function useEffectRef(callback: RefCallback): EffectRef; 27 | ``` 28 | 29 | Unlike `useRef` which is not responsive to element change, this hook provides ability to observe any element's mount and unmount. 30 | 31 | In case you need to use multiple callback refs on the same DOM element, `useMergedRef` from `@huse/merged-ref` may help. 32 | 33 | 34 | -------------------------------------------------------------------------------- /packages/intended-lazy/docs/useIntendedLazyValue.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useIntendedLazyValue 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Intended Lazy 8 | path: /intended-lazy 9 | order: 1 10 | --- 11 | 12 | # useIntendedLazyValue 13 | 14 | Wrap a value to a lazy evaluated function, this function is reference stable so that it won't trigger re-render to a component decorated with `memo`. 15 | 16 | **Note: The value is updated after commit phase, this means it's UNSAFE to read value in comonent's `render` and `useLayoutEffect` as well as its children's corresponding lifecycles.** 17 | 18 | ```typescript 19 | function useIntendedLazyValue(value: T): () => T 20 | ``` 21 | 22 | In most cases, this is a performance trick to reduce child component's re-renders when this value is used in a event callback triggered by user, by ensuering event callback is always invoked after DOM is updated, the value is almost always in sync. 23 | 24 | We intentionally naming this hook in this way to warn any users about its unsafety in practice, the React team is working on a better solution, till then we will deprecate this hook, a long name can help you to find out all references and replacement with the official solution. 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/performance/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@huse/performance", 3 | "version": "1.1.1", 4 | "keywords": [ 5 | "react", 6 | "hooks" 7 | ], 8 | "homepage": "https://github.com/ecomfe/react-hooks/tree/master/packages/performance", 9 | "bugs": { 10 | "url": "https://github.com/ecomfe/react-hooks/issues" 11 | }, 12 | "license": "MIT", 13 | "main": "cjs/index.js", 14 | "module": "es/index.js", 15 | "types": "es/index.d.ts", 16 | "files": [ 17 | "cjs", 18 | "es", 19 | "src" 20 | ], 21 | "scripts": { 22 | "build": "rm -rf es cjs && tsc & tsc --module ESNext --outDir ./es", 23 | "build-check": "tsc", 24 | "lint": "skr lint --strict src demo", 25 | "test": "skr test --coverage --target=react" 26 | }, 27 | "devDependencies": { 28 | "@reskript/cli": "^1.10.1", 29 | "@reskript/cli-lint": "^1.10.1", 30 | "@reskript/cli-test": "^1.10.1", 31 | "@reskript/config-lint": "^1.10.1", 32 | "@testing-library/react-hooks": "^7.0.1", 33 | "@types/react": "^17.0.14", 34 | "antd": "^4.16.8", 35 | "react": "^17.0.0", 36 | "typescript": "^4.3.5" 37 | }, 38 | "peerDependencies": { 39 | "react": ">=16.8.0" 40 | }, 41 | "publishConfig": { 42 | "access": "public", 43 | "registry": "https://registry.npmjs.com" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/transition-state/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@huse/transition-state", 3 | "version": "1.1.1", 4 | "keywords": [ 5 | "react", 6 | "hooks" 7 | ], 8 | "homepage": "https://github.com/ecomfe/react-hooks/tree/master/packages/transition-state", 9 | "bugs": { 10 | "url": "https://github.com/ecomfe/react-hooks/issues" 11 | }, 12 | "license": "MIT", 13 | "main": "cjs/index.js", 14 | "module": "es/index.js", 15 | "types": "es/index.d.ts", 16 | "files": [ 17 | "cjs", 18 | "es", 19 | "src" 20 | ], 21 | "scripts": { 22 | "build": "rm -rf es cjs && tsc & tsc --module ESNext --outDir ./es", 23 | "build-check": "tsc", 24 | "lint": "skr lint --strict src demo", 25 | "test": "skr test --coverage" 26 | }, 27 | "devDependencies": { 28 | "@reskript/cli": "^1.10.1", 29 | "@reskript/cli-lint": "^1.10.1", 30 | "@reskript/cli-test": "^1.10.1", 31 | "@reskript/config-lint": "^1.10.1", 32 | "@testing-library/react-hooks": "^7.0.1", 33 | "@types/react": "^17.0.14", 34 | "antd": "^4.16.8", 35 | "react": "^17.0.0", 36 | "typescript": "^4.3.5" 37 | }, 38 | "peerDependencies": { 39 | "react": ">=16.8.0" 40 | }, 41 | "publishConfig": { 42 | "access": "public", 43 | "registry": "https://registry.npmjs.com" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/media/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@huse/media", 3 | "version": "1.1.1", 4 | "keywords": [ 5 | "react", 6 | "hooks" 7 | ], 8 | "homepage": "https://github.com/ecomfe/react-hooks/tree/master/packages/media", 9 | "bugs": { 10 | "url": "https://github.com/ecomfe/react-hooks/issues" 11 | }, 12 | "license": "MIT", 13 | "main": "cjs/index.js", 14 | "module": "es/index.js", 15 | "types": "es/index.d.ts", 16 | "files": [ 17 | "cjs", 18 | "es", 19 | "src" 20 | ], 21 | "scripts": { 22 | "build": "rm -rf es cjs && tsc & tsc --module ESNext --outDir ./es", 23 | "build-check": "tsc", 24 | "lint": "skr lint --strict src demo", 25 | "start": "skr dev --src=demo", 26 | "test": "echo 'no test in @huse/media'" 27 | }, 28 | "devDependencies": { 29 | "@reskript/cli": "^1.10.1", 30 | "@reskript/cli-dev": "^1.10.1", 31 | "@reskript/cli-lint": "^1.10.1", 32 | "@reskript/config-lint": "^1.10.1", 33 | "@types/react": "^17.0.14", 34 | "classnames": "^2.2.6", 35 | "react": "^17.0.0", 36 | "typescript": "^4.3.5", 37 | "webpack": "^5.45.1" 38 | }, 39 | "peerDependencies": { 40 | "react": ">=16.8.0" 41 | }, 42 | "publishConfig": { 43 | "access": "public", 44 | "registry": "https://registry.npmjs.com" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/poll/demo/entries/index.js: -------------------------------------------------------------------------------- 1 | import {useRef, useCallback} from 'react'; 2 | import {render} from 'react-dom'; 3 | import {usePoll} from '../../src'; 4 | 5 | const options = { 6 | minInterval: 5 * 1000, 7 | maxInterval: 20 * 1000, 8 | stopOnInactive: true, 9 | maxIdleTime: 30 * 1000, 10 | }; 11 | 12 | const App = () => { 13 | const requestTime = useRef(new Date().toLocaleString()); 14 | const responseTime = useRef(new Date().toLocaleString()); 15 | const fetch = useCallback( 16 | async () => { 17 | requestTime.current = new Date().toLocaleString(); 18 | await new Promise(resolve => setTimeout(resolve, 1200)); 19 | responseTime.current = new Date().toLocaleString(); 20 | return Math.random(); 21 | }, 22 | [] 23 | ); 24 | const [value, pendingCount] = usePoll(fetch, options); 25 | 26 | return ( 27 |
28 |

Request Start At: {requestTime.current}

29 |

Response Arrived At: {responseTime.current}

30 |

Random Value: {value}

31 |

Current State: {pendingCount ? 'Fetching' : 'Waiting'}

32 |
33 | ); 34 | }; 35 | 36 | render( 37 | , 38 | document.body.appendChild(document.createElement('div')) 39 | ); 40 | -------------------------------------------------------------------------------- /packages/router/docs/demo/useSearchParamState.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback} from 'react'; 2 | import {Checkbox, Button} from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import {MemoryRouter, useLocation} from 'react-router-dom'; 5 | import {useLocationState, useSearchParamState} from '@huse/router'; 6 | 7 | export default () => { 8 | const App = () => { 9 | const location = useLocation(); 10 | const [page, setPage] = useSearchParamState('page'); 11 | const pageIndex = parseInt(page || '0', 10); 12 | const nextPage = useCallback( 13 | () => setPage((pageIndex + 1).toString()), 14 | [pageIndex, setPage] 15 | ); 16 | const start = pageIndex * 10 + 1; 17 | const items = Array.from({length: 10}, (v, i) => ({id: start + i, name: `Item ${start + i}`})); 18 | return ( 19 | <> 20 |

{location.pathname}{location.search}

21 |
    22 | {items.map(i =>
  • {i.name}
  • )} 23 |
24 | 25 | 26 | ); 27 | }; 28 | return ( 29 | 30 | 31 | 32 | ); 33 | }; -------------------------------------------------------------------------------- /packages/window-size/src/index.ts: -------------------------------------------------------------------------------- 1 | import {useState, useEffect} from 'react'; 2 | 3 | export interface WindowSize { 4 | innerWidth: number; 5 | innerHeight: number; 6 | outerWidth: number; 7 | outerHeight: number; 8 | } 9 | 10 | const getWindowSize = (): WindowSize => { 11 | /* istanbul ignore next */ 12 | if (typeof window === 'undefined') { 13 | return { 14 | innerWidth: 0, 15 | innerHeight: 0, 16 | outerWidth: 0, 17 | outerHeight: 0, 18 | }; 19 | } 20 | return { 21 | innerWidth: window.innerWidth, 22 | innerHeight: window.innerHeight, 23 | outerWidth: window.outerWidth, 24 | outerHeight: window.outerHeight, 25 | }; 26 | }; 27 | 28 | export function useWindowSize(): WindowSize { 29 | const [size, setSize] = useState(getWindowSize()); 30 | useEffect( 31 | () => { 32 | /* istanbul ignore else */ 33 | if (typeof window !== 'undefined') { 34 | const updateWindowSize = () => setSize(getWindowSize()); 35 | window.addEventListener('resize', updateWindowSize); 36 | return () => window.removeEventListener('resize', updateWindowSize); 37 | } 38 | }, 39 | [] 40 | ); 41 | return size; 42 | } 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # reskript build assets 64 | /*/*/es 65 | /*/*/cjs 66 | packages/doc/.docz 67 | .DS_Store 68 | /.umi 69 | /.umi-production 70 | /dist 71 | -------------------------------------------------------------------------------- /packages/timeout/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@huse/timeout", 3 | "version": "1.1.1", 4 | "keywords": [ 5 | "react", 6 | "hooks" 7 | ], 8 | "homepage": "https://github.com/ecomfe/react-hooks/tree/master/packages/timeout", 9 | "bugs": { 10 | "url": "https://github.com/ecomfe/react-hooks/issues" 11 | }, 12 | "license": "MIT", 13 | "main": "cjs/index.js", 14 | "module": "es/index.js", 15 | "types": "es/index.d.ts", 16 | "files": [ 17 | "cjs", 18 | "es", 19 | "src" 20 | ], 21 | "scripts": { 22 | "build": "rm -rf es cjs && tsc & tsc --module ESNext --outDir ./es", 23 | "build-check": "tsc", 24 | "lint": "skr lint --strict src demo", 25 | "test": "skr test --coverage" 26 | }, 27 | "devDependencies": { 28 | "@reskript/cli": "^1.10.1", 29 | "@reskript/cli-lint": "^1.10.1", 30 | "@reskript/cli-test": "^1.10.1", 31 | "@reskript/config-lint": "^1.10.1", 32 | "@testing-library/react-hooks": "^7.0.1", 33 | "@types/react": "^17.0.14", 34 | "antd": "^4.16.8", 35 | "react": "^17.0.0", 36 | "react-test-renderer": "^17.0.0", 37 | "typescript": "^4.3.5" 38 | }, 39 | "peerDependencies": { 40 | "react": ">=16.8.0" 41 | }, 42 | "publishConfig": { 43 | "access": "public", 44 | "registry": "https://registry.npmjs.com" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/hover/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@huse/hover", 3 | "version": "1.1.2", 4 | "keywords": [ 5 | "react", 6 | "hooks" 7 | ], 8 | "homepage": "https://github.com/ecomfe/react-hooks/tree/master/packages/hover", 9 | "bugs": { 10 | "url": "https://github.com/ecomfe/react-hooks/issues" 11 | }, 12 | "license": "MIT", 13 | "main": "cjs/index.js", 14 | "module": "es/index.js", 15 | "types": "es/index.d.ts", 16 | "files": [ 17 | "cjs", 18 | "es", 19 | "src" 20 | ], 21 | "scripts": { 22 | "build": "rm -rf es cjs && tsc & tsc --module ESNext --outDir ./es", 23 | "build-check": "tsc", 24 | "lint": "skr lint --strict src demo", 25 | "test": "skr test --coverage --target=react" 26 | }, 27 | "dependencies": { 28 | "@huse/boolean": "^1.1.2" 29 | }, 30 | "devDependencies": { 31 | "@reskript/cli": "^1.10.1", 32 | "@reskript/cli-lint": "^1.10.1", 33 | "@reskript/cli-test": "^1.10.1", 34 | "@reskript/config-lint": "^1.10.1", 35 | "@testing-library/react-hooks": "^7.0.1", 36 | "@types/react": "^17.0.14", 37 | "react": "^17.0.0", 38 | "typescript": "^4.3.5" 39 | }, 40 | "peerDependencies": { 41 | "react": ">=16.8.0" 42 | }, 43 | "publishConfig": { 44 | "access": "public", 45 | "registry": "https://registry.npmjs.com" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/request/docs/useRequestCallback.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useRequestCallback 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Request 8 | path: /request 9 | order: 3 10 | --- 11 | 12 | # useRequestCallback 13 | 14 | Like `useReqeust` but instead of trigger request automatically this hook returns a function (`() => void`) to allow manual trigger of request. 15 | 16 | ```typescript 17 | function useRequestCallback(task: Request, params: K, options?: RequestOptions): [() => void, RequestResult] 18 | ``` 19 | 20 | This allows to attach request with user interaction like button clicks. 21 | 22 | ```javascript 23 | import {Spin} from 'antd'; 24 | import {noop} from 'lodash'; 25 | import {useRequestCallback} from '@huse/request'; 26 | 27 | const api = id => fetch(`/users/${id}`).then(r => r.json()); 28 | 29 | const App = ({user}) => { 30 | const [request, result] = useRequestCallback(api, {id: user.id}); 31 | 32 | if (result.pending || !result.data) { 33 | return ( 34 |
35 | show detail 36 | {!!result.pending && } 37 | 38 | ); 39 | } 40 | 41 | return ( 42 |
43 | username: {result.data.username} 44 |
45 | ); 46 | }; -------------------------------------------------------------------------------- /packages/input-value/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@huse/input-value", 3 | "version": "1.1.1", 4 | "keywords": [ 5 | "react", 6 | "hooks" 7 | ], 8 | "homepage": "https://github.com/ecomfe/react-hooks/tree/master/packages/input-value", 9 | "bugs": { 10 | "url": "https://github.com/ecomfe/react-hooks/issues" 11 | }, 12 | "license": "MIT", 13 | "main": "cjs/index.js", 14 | "module": "es/index.js", 15 | "types": "es/index.d.ts", 16 | "files": [ 17 | "cjs", 18 | "es", 19 | "src" 20 | ], 21 | "scripts": { 22 | "build": "rm -rf es cjs && tsc & tsc --module ESNext --outDir ./es", 23 | "build-check": "tsc", 24 | "lint": "skr lint --strict src demo", 25 | "test": "skr test --coverage" 26 | }, 27 | "devDependencies": { 28 | "@reskript/cli": "^1.10.1", 29 | "@reskript/cli-lint": "^1.10.1", 30 | "@reskript/cli-test": "^1.10.1", 31 | "@reskript/config-lint": "^1.10.1", 32 | "@testing-library/react-hooks": "^7.0.1", 33 | "@types/react": "^17.0.14", 34 | "antd": "^4.16.8", 35 | "react": "^17.0.0", 36 | "react-test-renderer": "^17.0.0", 37 | "typescript": "^4.3.5" 38 | }, 39 | "peerDependencies": { 40 | "react": ">=16.8.0" 41 | }, 42 | "publishConfig": { 43 | "access": "public", 44 | "registry": "https://registry.npmjs.com" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/scroll-lock/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@huse/scroll-lock", 3 | "version": "1.1.1", 4 | "keywords": [ 5 | "react", 6 | "hooks" 7 | ], 8 | "homepage": "https://github.com/ecomfe/react-hooks/tree/master/packages/scroll-lock", 9 | "bugs": { 10 | "url": "https://github.com/ecomfe/react-hooks/issues" 11 | }, 12 | "license": "MIT", 13 | "main": "cjs/index.js", 14 | "module": "es/index.js", 15 | "types": "es/index.d.ts", 16 | "files": [ 17 | "cjs", 18 | "es", 19 | "src" 20 | ], 21 | "scripts": { 22 | "build": "rm -rf es cjs && tsc & tsc --module ESNext --outDir ./es", 23 | "build-check": "tsc", 24 | "lint": "skr lint --strict src demo", 25 | "test": "skr test --coverage --target=react" 26 | }, 27 | "devDependencies": { 28 | "@reskript/cli": "^1.10.1", 29 | "@reskript/cli-lint": "^1.10.1", 30 | "@reskript/cli-test": "^1.10.1", 31 | "@reskript/config-lint": "^1.10.1", 32 | "@testing-library/react-hooks": "^7.0.1", 33 | "@types/react": "^17.0.14", 34 | "antd": "^4.16.8", 35 | "enzyme": "^3.11.0", 36 | "react": "^17.0.0", 37 | "typescript": "^4.3.5" 38 | }, 39 | "peerDependencies": { 40 | "react": ">=16.8.0" 41 | }, 42 | "publishConfig": { 43 | "access": "public", 44 | "registry": "https://registry.npmjs.com" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/derived-state/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@huse/derived-state", 3 | "version": "1.1.1", 4 | "keywords": [ 5 | "react", 6 | "hooks" 7 | ], 8 | "homepage": "https://github.com/ecomfe/react-hooks/tree/master/packages/derived-state", 9 | "bugs": { 10 | "url": "https://github.com/ecomfe/react-hooks/issues" 11 | }, 12 | "license": "MIT", 13 | "main": "cjs/index.js", 14 | "module": "es/index.js", 15 | "types": "es/index.d.ts", 16 | "files": [ 17 | "cjs", 18 | "es", 19 | "src" 20 | ], 21 | "scripts": { 22 | "build": "rm -rf es cjs && tsc & tsc --module ESNext --outDir ./es", 23 | "build-check": "tsc", 24 | "lint": "skr lint --strict src demo", 25 | "test": "skr test --coverage" 26 | }, 27 | "devDependencies": { 28 | "@reskript/cli": "^1.10.1", 29 | "@reskript/cli-lint": "^1.10.1", 30 | "@reskript/cli-test": "^1.10.1", 31 | "@reskript/config-lint": "^1.10.1", 32 | "@testing-library/react-hooks": "^7.0.1", 33 | "@types/react": "^17.0.14", 34 | "antd": "^4.16.8", 35 | "react": "^17.0.0", 36 | "react-test-renderer": "^17.0.0", 37 | "typescript": "^4.3.5" 38 | }, 39 | "peerDependencies": { 40 | "react": ">=16.8.0" 41 | }, 42 | "publishConfig": { 43 | "access": "public", 44 | "registry": "https://registry.npmjs.com" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/intended-lazy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@huse/intended-lazy", 3 | "version": "1.1.2", 4 | "keywords": [ 5 | "react", 6 | "hooks" 7 | ], 8 | "homepage": "https://github.com/ecomfe/react-hooks/tree/master/packages/intended-lazy", 9 | "bugs": { 10 | "url": "https://github.com/ecomfe/react-hooks/issues" 11 | }, 12 | "license": "MIT", 13 | "main": "cjs/index.js", 14 | "module": "es/index.js", 15 | "types": "es/index.d.ts", 16 | "files": [ 17 | "cjs", 18 | "es", 19 | "src" 20 | ], 21 | "scripts": { 22 | "build": "rm -rf es cjs && tsc & tsc --module ESNext --outDir ./es", 23 | "build-check": "tsc", 24 | "lint": "skr lint --strict src demo", 25 | "test": "skr test --coverage" 26 | }, 27 | "devDependencies": { 28 | "@reskript/cli": "^1.10.1", 29 | "@reskript/cli-lint": "^1.10.1", 30 | "@reskript/cli-test": "^1.10.1", 31 | "@reskript/config-lint": "^1.10.1", 32 | "@testing-library/react-hooks": "^7.0.1", 33 | "@types/react": "^17.0.14", 34 | "antd": "^4.16.8", 35 | "react": "^17.0.0", 36 | "react-test-renderer": "^17.0.0", 37 | "typescript": "^4.3.5" 38 | }, 39 | "peerDependencies": { 40 | "react": ">=16.8.0" 41 | }, 42 | "publishConfig": { 43 | "access": "public", 44 | "registry": "https://registry.npmjs.com" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/merged-ref/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@huse/merged-ref", 3 | "version": "1.2.1", 4 | "keywords": [ 5 | "react", 6 | "hooks" 7 | ], 8 | "homepage": "https://github.com/ecomfe/react-hooks/tree/master/packages/merged-ref", 9 | "bugs": { 10 | "url": "https://github.com/ecomfe/react-hooks/issues" 11 | }, 12 | "license": "MIT", 13 | "main": "cjs/index.js", 14 | "module": "es/index.js", 15 | "types": "es/index.d.ts", 16 | "files": [ 17 | "cjs", 18 | "es", 19 | "src" 20 | ], 21 | "scripts": { 22 | "build": "rm -rf es cjs && tsc & tsc --module ESNext --outDir ./es", 23 | "build-check": "tsc", 24 | "lint": "skr lint --strict src demo", 25 | "test": "skr test --coverage" 26 | }, 27 | "dependencies": { 28 | "@huse/previous-value": "^1.1.1" 29 | }, 30 | "devDependencies": { 31 | "@reskript/cli": "^1.10.1", 32 | "@reskript/cli-lint": "^1.10.1", 33 | "@reskript/cli-test": "^1.10.1", 34 | "@reskript/config-lint": "^1.10.1", 35 | "@testing-library/react-hooks": "^7.0.1", 36 | "@types/react": "^17.0.14", 37 | "react": "^17.0.0", 38 | "typescript": "^4.3.5" 39 | }, 40 | "peerDependencies": { 41 | "react": ">=16.8.0" 42 | }, 43 | "publishConfig": { 44 | "access": "public", 45 | "registry": "https://registry.npmjs.com" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/document-event/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@huse/document-event", 3 | "version": "1.1.1", 4 | "keywords": [ 5 | "react", 6 | "hooks" 7 | ], 8 | "homepage": "https://github.com/ecomfe/react-hooks/tree/master/packages/document-event", 9 | "bugs": { 10 | "url": "https://github.com/ecomfe/react-hooks/issues" 11 | }, 12 | "license": "MIT", 13 | "main": "cjs/index.js", 14 | "module": "es/index.js", 15 | "types": "es/index.d.ts", 16 | "files": [ 17 | "cjs", 18 | "es", 19 | "src" 20 | ], 21 | "scripts": { 22 | "build": "rm -rf es cjs && tsc & tsc --module ESNext --outDir ./es", 23 | "build-check": "tsc", 24 | "lint": "skr lint --strict src demo", 25 | "test": "skr test --coverage --target=react" 26 | }, 27 | "devDependencies": { 28 | "@reskript/cli": "^1.10.1", 29 | "@reskript/cli-lint": "^1.10.1", 30 | "@reskript/cli-test": "^1.10.1", 31 | "@reskript/config-lint": "^1.10.1", 32 | "@testing-library/react": "^12.0.0", 33 | "@types/react": "^17.0.14", 34 | "enzyme": "^3.11.0", 35 | "react": "^17.0.0", 36 | "react-dom": "^17.0.0", 37 | "typescript": "^4.3.5" 38 | }, 39 | "peerDependencies": { 40 | "react": ">=16.8.0" 41 | }, 42 | "publishConfig": { 43 | "access": "public", 44 | "registry": "https://registry.npmjs.com" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/immer/docs/demo/useImmerReducer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Button} from 'antd'; 3 | import 'antd/dist/antd.min.css'; 4 | import {useImmerReducer} from '@huse/immer'; 5 | 6 | export default () => { 7 | const [state, dispatch] = useImmerReducer( 8 | (state, action) => { 9 | switch (action.type) { 10 | case 'inc': 11 | state.value++; 12 | break; 13 | case 'dec': 14 | state.value--; 15 | break; 16 | case 'reset': 17 | return {value: 0}; 18 | default: 19 | return state; 20 | } 21 | }, 22 | {value: 0} 23 | ); 24 | return ( 25 | <> 26 |

current value: {state.value}

27 |
28 | 29 | 30 | 31 |
32 | 33 | ); 34 | }; -------------------------------------------------------------------------------- /packages/methods/docs/useMethodsExtension.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useMethodsExtension 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Methods 8 | path: /methods 9 | order: 3 10 | --- 11 | 12 | # useMethodsExtension 13 | 14 | Once you have a `setState` function from `useImmer` in [`use-immer`](https://github.com/immerjs/use-immer/), you can also wrap it to a methods object. 15 | 16 | ```typescript 17 | export function useMethodsExtension>(reducers: R, setState: SetImmerState): Methods 18 | ``` 19 | 20 | This hook is also useful to extends more methods from an already generated methods hook. 21 | 22 | ```javascript 23 | const App = () => { 24 | // Suppose useArray is a hook implemented on useMethods 25 | const [list, methods, setList] = useArray(); 26 | const extendedMethods = useMethodsExtension( 27 | { 28 | filterEnabled(state) { 29 | return state.filter(u => u.enabled); 30 | }, 31 | }, 32 | setList 33 | ); 34 | 35 | // Now filterEnabled becomes a method to update list 36 | return ( 37 | <> 38 | {/* other content */} 39 | 42 | 43 | ); 44 | }; 45 | ``` 46 | -------------------------------------------------------------------------------- /packages/scroll-into-view/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@huse/scroll-into-view", 3 | "version": "1.1.1", 4 | "keywords": [ 5 | "react", 6 | "hooks" 7 | ], 8 | "homepage": "https://github.com/ecomfe/react-hooks/tree/master/packages/scroll-into-view", 9 | "bugs": { 10 | "url": "https://github.com/ecomfe/react-hooks/issues" 11 | }, 12 | "license": "MIT", 13 | "main": "cjs/index.js", 14 | "module": "es/index.js", 15 | "types": "es/index.d.ts", 16 | "files": [ 17 | "cjs", 18 | "es", 19 | "src" 20 | ], 21 | "scripts": { 22 | "build": "rm -rf es cjs && tsc & tsc --module ESNext --outDir ./es", 23 | "build-check": "tsc", 24 | "lint": "skr lint --strict src demo", 25 | "start": "skr dev --src=demo", 26 | "test": "echo 'no test in @huse/scroll-into-view'" 27 | }, 28 | "devDependencies": { 29 | "@reskript/cli": "^1.10.1", 30 | "@reskript/cli-dev": "^1.10.1", 31 | "@reskript/cli-lint": "^1.10.1", 32 | "@reskript/config-lint": "^1.10.1", 33 | "@types/react": "^17.0.14", 34 | "classnames": "^2.2.6", 35 | "react": "^17.0.0", 36 | "typescript": "^4.3.5", 37 | "webpack": "^5.45.1" 38 | }, 39 | "peerDependencies": { 40 | "react": ">=16.8.0" 41 | }, 42 | "publishConfig": { 43 | "access": "public", 44 | "registry": "https://registry.npmjs.com" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/methods/src/native.ts: -------------------------------------------------------------------------------- 1 | import {useRef, Dispatch, SetStateAction, useState} from 'react'; 2 | import {NativeReducers, Methods} from './interface'; 3 | 4 | export type NativeMethodsHook> = [S, Methods, Dispatch>]; 5 | 6 | export function useMethodsExtensionNative>( 7 | reducers: R, 8 | setState: Dispatch> 9 | ): Methods { 10 | const methodsRef = useRef | undefined>(undefined); 11 | 12 | if (!methodsRef.current) { 13 | methodsRef.current = Object.keys(reducers).reduce( 14 | (methods, key) => { 15 | const fn = reducers[key]; 16 | const bound = (...args: any[]) => setState(s => fn(s, ...args)); 17 | Object.assign(methods, {[key]: bound}); 18 | return methods; 19 | }, 20 | {} as Methods 21 | ); 22 | } 23 | 24 | return methodsRef.current; 25 | } 26 | 27 | export function useMethodsNative>( 28 | reducers: R, 29 | initialState: S | (() => S) 30 | ): NativeMethodsHook { 31 | const [state, setState] = useState(initialState); 32 | const boundMethods = useMethodsExtensionNative(reducers, setState); 33 | return [state, boundMethods, setState]; 34 | } 35 | -------------------------------------------------------------------------------- /packages/snapshot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@huse/snapshot", 3 | "version": "1.1.1", 4 | "keywords": [ 5 | "react", 6 | "hooks" 7 | ], 8 | "homepage": "https://github.com/ecomfe/react-hooks/tree/master/packages/snapshot", 9 | "bugs": { 10 | "url": "https://github.com/ecomfe/react-hooks/issues" 11 | }, 12 | "license": "MIT", 13 | "main": "cjs/index.js", 14 | "module": "es/index.js", 15 | "types": "es/index.d.ts", 16 | "files": [ 17 | "cjs", 18 | "es", 19 | "src" 20 | ], 21 | "scripts": { 22 | "build": "rm -rf es cjs && tsc & tsc --module ESNext --outDir ./es", 23 | "build-check": "tsc", 24 | "lint": "skr lint --strict src demo", 25 | "test": "skr test --coverage" 26 | }, 27 | "dependencies": { 28 | "@huse/debounce": "^1.1.1" 29 | }, 30 | "devDependencies": { 31 | "@reskript/cli": "^1.10.1", 32 | "@reskript/cli-lint": "^1.10.1", 33 | "@reskript/cli-test": "^1.10.1", 34 | "@reskript/config-lint": "^1.10.1", 35 | "@testing-library/react-hooks": "^7.0.1", 36 | "@types/react": "^17.0.14", 37 | "antd": "^4.16.8", 38 | "react": "^17.0.0", 39 | "typescript": "^4.3.5" 40 | }, 41 | "peerDependencies": { 42 | "react": ">=16.8.0" 43 | }, 44 | "publishConfig": { 45 | "access": "public", 46 | "registry": "https://registry.npmjs.com" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/update/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@huse/update", 3 | "version": "1.1.1", 4 | "keywords": [ 5 | "react", 6 | "hooks" 7 | ], 8 | "homepage": "https://github.com/ecomfe/react-hooks/tree/master/packages/update", 9 | "bugs": { 10 | "url": "https://github.com/ecomfe/react-hooks/issues" 11 | }, 12 | "license": "MIT", 13 | "main": "cjs/index.js", 14 | "module": "es/index.js", 15 | "types": "es/index.d.ts", 16 | "files": [ 17 | "cjs", 18 | "es", 19 | "src" 20 | ], 21 | "scripts": { 22 | "build": "rm -rf es cjs && tsc & tsc --module ESNext --outDir ./es", 23 | "build-check": "tsc", 24 | "lint": "skr lint --strict src demo", 25 | "test": "skr test --coverage" 26 | }, 27 | "devDependencies": { 28 | "@reskript/cli": "^1.10.1", 29 | "@reskript/cli-lint": "^1.10.1", 30 | "@reskript/cli-test": "^1.10.1", 31 | "@reskript/config-lint": "^1.10.1", 32 | "@testing-library/react-hooks": "^7.0.1", 33 | "@types/react": "^17.0.14", 34 | "antd": "^4.16.8", 35 | "react": "^17.0.0", 36 | "react-dom": "^17.0.0", 37 | "react-test-renderer": "^17.0.0", 38 | "typescript": "^4.3.5" 39 | }, 40 | "peerDependencies": { 41 | "react": ">=16.8.0" 42 | }, 43 | "publishConfig": { 44 | "access": "public", 45 | "registry": "https://registry.npmjs.com" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/effect-ref/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@huse/effect-ref", 3 | "version": "1.1.1", 4 | "keywords": [ 5 | "react", 6 | "hooks" 7 | ], 8 | "homepage": "https://github.com/ecomfe/react-hooks/tree/master/packages/effect-ref", 9 | "bugs": { 10 | "url": "https://github.com/ecomfe/react-hooks/issues" 11 | }, 12 | "license": "MIT", 13 | "main": "cjs/index.js", 14 | "module": "es/index.js", 15 | "types": "es/index.d.ts", 16 | "files": [ 17 | "cjs", 18 | "es", 19 | "src" 20 | ], 21 | "scripts": { 22 | "build": "rm -rf es cjs && tsc & tsc --module ESNext --outDir ./es", 23 | "build-check": "tsc", 24 | "lint": "skr lint --strict src demo", 25 | "test": "skr test --coverage --target=react" 26 | }, 27 | "devDependencies": { 28 | "@reskript/cli": "^1.10.1", 29 | "@reskript/cli-lint": "^1.10.1", 30 | "@reskript/cli-test": "^1.10.1", 31 | "@reskript/config-lint": "^1.10.1", 32 | "@testing-library/react": "^12.0.0", 33 | "@types/react": "^17.0.14", 34 | "antd": "^4.16.8", 35 | "enzyme": "^3.11.0", 36 | "react": "^17.0.0", 37 | "react-dom": "^17.0.0", 38 | "typescript": "^4.3.5" 39 | }, 40 | "peerDependencies": { 41 | "react": ">=16.8.0" 42 | }, 43 | "publishConfig": { 44 | "access": "public", 45 | "registry": "https://registry.npmjs.com" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/scroll-position/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@huse/scroll-position", 3 | "version": "1.1.1", 4 | "keywords": [ 5 | "react", 6 | "hooks" 7 | ], 8 | "homepage": "https://github.com/ecomfe/react-hooks/tree/master/packages/scroll-position", 9 | "bugs": { 10 | "url": "https://github.com/ecomfe/react-hooks/issues" 11 | }, 12 | "license": "MIT", 13 | "main": "cjs/index.js", 14 | "module": "es/index.js", 15 | "types": "es/index.d.ts", 16 | "files": [ 17 | "cjs", 18 | "es", 19 | "src" 20 | ], 21 | "scripts": { 22 | "build": "rm -rf es cjs && tsc & tsc --module ESNext --outDir ./es", 23 | "build-check": "tsc", 24 | "lint": "skr lint --strict src demo", 25 | "test": "skr test --coverage --target=react" 26 | }, 27 | "dependencies": { 28 | "has-passive-events": "^1.0.0" 29 | }, 30 | "devDependencies": { 31 | "@reskript/cli": "^1.10.1", 32 | "@reskript/cli-lint": "^1.10.1", 33 | "@reskript/cli-test": "^1.10.1", 34 | "@reskript/config-lint": "^1.10.1", 35 | "@testing-library/react-hooks": "^7.0.1", 36 | "@types/react": "^17.0.14", 37 | "react": "^17.0.0", 38 | "typescript": "^4.3.5" 39 | }, 40 | "peerDependencies": { 41 | "react": ">=16.8.0" 42 | }, 43 | "publishConfig": { 44 | "access": "public", 45 | "registry": "https://registry.npmjs.com" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/selection/docs/useSelection.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useSelection 3 | nav: 4 | title: Hooks 5 | path: /hook 6 | group: 7 | title: Selection 8 | path: /selection 9 | order: 2 10 | --- 11 | 12 | # useSelection 13 | 14 | This hook provides fundamental capabilities to select single, multiple or a range of items in a list. 15 | 16 | ```typescript 17 | interface SelectionOptions { 18 | multiple?: boolean; // allow multiple selection using CMD or CTRL 19 | range?: boolean; // allow range selection using SHIFT 20 | } 21 | 22 | interface ClickContext { 23 | ctrlKey: boolean; 24 | metaKey: boolean; 25 | shiftKey: boolean; 26 | } 27 | 28 | interface SelectionMethods { 29 | selectIndex(index: number, e?: ClickContext): void; 30 | } 31 | 32 | type SelectionHook = [number[], SelectionMethods]; 33 | 34 | function useSelection(initialSelection: number[] = [], options?: SelectionOptions): SelectionHook; 35 | ``` 36 | 37 | `useSelection` is an index based interface where the content of list is not important. 38 | The returned number array contains a set of selected zero-based item index. 39 | 40 | The `selectIndex` function in `SelectionMethods` accepts an zero-based index and a `ClickContext`, 41 | usually `MouseEvent` instance are compatible with `ClickContext` so you can directly pass an event object to it. 42 | 43 | 44 | -------------------------------------------------------------------------------- /docs/index.en-US.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "huse - React Hooks Library" 3 | hero: 4 | title: huse 5 | desc: Hook To Use. 6 | actions: 7 | - text: Getting Started 8 | link: /docs/getting-started 9 | features: 10 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/813f5ed9-6bc4-43d4-9f74-ec81ecf35733/k7htg6n4_w144_h144.png 11 | title: Rich Collection 12 | desc: huse core contains a large set of essential react hooks, with demos and examples for each one of them. 13 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/7659205c-6637-4fa2-8529-d32e5818304b/k7htflfb_w144_h144.png 14 | title: Detailed Tutorial 15 | desc: Providing tutorials about using hooks in practice. An all-in-one place to learn for pro coders and newcomers. 16 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/6319a122-e8b8-497f-9b45-37cfbe77edaa/k7htfx7t_w144_h144.png 17 | title: Ready for Production 18 | desc: Used in hundreds of products in Baidu, with high quality and robustness. 19 | footer: Open-source MIT Licensed | Copyright © 2020
Powered by [Baidu EFE team](https://ecomfe.github.io/) 20 | --- 21 | 22 | ## Easy to get started 23 | 24 | Install dependency: 25 | 26 | ```bash 27 | npm install huse --save 28 | ``` 29 | 30 | Or with `yarn`: 31 | 32 | ```bash 33 | yarn add huse 34 | ``` 35 | 36 | Use Hooks 37 | 38 | ```bash 39 | import { useBoolean } from 'huse'; 40 | ``` 41 | -------------------------------------------------------------------------------- /packages/document-title/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@huse/document-title", 3 | "version": "1.1.1", 4 | "keywords": [ 5 | "react", 6 | "hooks" 7 | ], 8 | "homepage": "https://github.com/ecomfe/react-hooks/tree/master/packages/document-title", 9 | "bugs": { 10 | "url": "https://github.com/ecomfe/react-hooks/issues" 11 | }, 12 | "license": "MIT", 13 | "main": "cjs/index.js", 14 | "module": "es/index.js", 15 | "types": "es/index.d.ts", 16 | "files": [ 17 | "cjs", 18 | "es", 19 | "src" 20 | ], 21 | "scripts": { 22 | "build": "rm -rf es cjs && tsc & tsc --module ESNext --outDir ./es", 23 | "build-check": "tsc", 24 | "lint": "skr lint --strict src demo", 25 | "test": "skr test --coverage --target=react" 26 | }, 27 | "devDependencies": { 28 | "@reskript/cli": "^1.10.1", 29 | "@reskript/cli-lint": "^1.10.1", 30 | "@reskript/cli-test": "^1.10.1", 31 | "@reskript/config-lint": "^1.10.1", 32 | "@testing-library/react": "^12.0.0", 33 | "@types/react": "^17.0.14", 34 | "antd": "^4.16.8", 35 | "enzyme": "^3.11.0", 36 | "react": "^17.0.0", 37 | "react-dom": "^17.0.0", 38 | "typescript": "^4.3.5" 39 | }, 40 | "peerDependencies": { 41 | "react": ">=16.8.0" 42 | }, 43 | "publishConfig": { 44 | "access": "public", 45 | "registry": "https://registry.npmjs.com" 46 | } 47 | } 48 | --------------------------------------------------------------------------------