├── .editorconfig ├── .gitignore ├── .nvmrc ├── .prettierignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── README.md ├── index │ └── README.md ├── modules.md ├── use-composed-event-handlers │ ├── README.md │ └── functions │ │ ├── composeEventHandlers.md │ │ └── useComposedEventHandlers.md ├── use-composed-refs │ ├── README.md │ ├── functions │ │ ├── assignRef.md │ │ └── useComposedRefs.md │ └── type-aliases │ │ └── AssignableRef.md ├── use-constant │ ├── README.md │ └── functions │ │ └── useConstant.md ├── use-controllable-state │ ├── README.md │ └── functions │ │ └── useControllableState.md ├── use-effect-event │ ├── README.md │ └── functions │ │ └── useEffectEvent.md ├── use-event-listener │ ├── README.md │ ├── functions │ │ └── useEventListener.md │ └── interfaces │ │ └── UseEventListenerOptions.md ├── use-force-update │ ├── README.md │ └── functions │ │ └── useForceUpdate.md ├── use-interval │ ├── README.md │ └── functions │ │ └── useInterval.md ├── use-is-hydrated │ ├── README.md │ └── functions │ │ └── useIsHydrated.md ├── use-layout-effect │ ├── README.md │ └── functions │ │ └── useLayoutEffect.md ├── use-map │ ├── README.md │ ├── functions │ │ └── useMap.md │ └── interfaces │ │ └── ReactiveMap.md ├── use-match-media │ ├── README.md │ ├── functions │ │ └── useMatchMedia.md │ └── interfaces │ │ ├── QueryObject.md │ │ └── UseMatchMediaOptions.md ├── use-prefers-reduced-motion │ ├── README.md │ └── functions │ │ └── usePrefersReducedMotion.md ├── use-promise │ ├── README.md │ └── functions │ │ └── usePromise.md ├── use-set │ ├── README.md │ ├── functions │ │ └── useSet.md │ └── interfaces │ │ └── ReactiveSet.md ├── use-state-with-history │ ├── README.md │ ├── functions │ │ └── useStateWithHistory.md │ ├── interfaces │ │ └── UseStateWithHistoryOptions.md │ └── type-aliases │ │ └── HistoryState.md └── use-timeout │ ├── README.md │ └── functions │ └── useTimeout.md ├── eslint.config.js ├── package.json ├── pnpm-lock.yaml ├── src ├── index.ts ├── lib │ └── json2mq.ts ├── test │ ├── setup.ts │ └── use-state-with-history.test.tsx ├── use-composed-event-handlers.ts ├── use-composed-refs.ts ├── use-constant.ts ├── use-controllable-state.ts ├── use-effect-event.ts ├── use-event-listener.ts ├── use-force-update.ts ├── use-interval.ts ├── use-is-hydrated.ts ├── use-layout-effect.ts ├── use-map.ts ├── use-match-media.ts ├── use-prefers-reduced-motion.ts ├── use-promise.ts ├── use-set.ts ├── use-state-with-history.ts └── use-timeout.ts ├── test └── .gitkeep ├── tsconfig.json ├── tsup.config.ts └── vite.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.yml] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log* 3 | node_modules 4 | /coverage 5 | /dist 6 | /haters 7 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /dist/**/* 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v0.6.0 2 | 3 | ### Breaking Changes 4 | 5 | - The `useEventListener` API has changed 6 | - Arguments are no longer variadic, meaning that the first argument must 7 | always point to a node or ref to a node rather than defaulting to `window` - 8 | - The node reference can now be a ref, a stateful node, or a string reference 9 | to `"window"` or `"document"` for global listeners. 10 | - The `options` object now accepts a `skip` property which allows you to skip 11 | adding the event listener under certain conditions. 12 | - Removed the `effectHook` option. In practice it has not been particularly 13 | useful and has been removed to simplify the API. 14 | 15 | ## v0.5.0 16 | 17 | ### Breaking Changes 18 | 19 | - Removed `usePrevious` hook 20 | - Renamed `useIsomorphicLayoutEffect` to `useLayoutEffect`. No more alias! 21 | - Removed `useStableCallback` in favor of `useEffectEvent` 22 | 23 | ### Other Changes 24 | 25 | - Fixed export maps to support importing individual hook modules 26 | 27 | ## v0.4.0 28 | 29 | ### Breaking Changes 30 | 31 | - `useInterval` and `useTimeout` will now cancel their timers if `null` or 32 | `undefined` is passed as the time value. For the old behavior, pass `0` 33 | instead. 34 | - Bumped build output target from ES2019 to ES2020 35 | 36 | ### Other Changes 37 | 38 | - Added `useComposedEventHandlers`, `useComposedRefs` 39 | - Updated build dependencies 40 | 41 | ## v0.3.1 42 | 43 | ### Bug-fixes 44 | 45 | - `useStateWithHistory`: Do not update state or history state if the new value 46 | is the same as the current value 47 | - Fixed known bugs with `usePrevious` and un-marked it for deprecation. We no 48 | longer return a ref value (which is not stateful, won't trigger effects and is 49 | not safe in React 18), but a stateful value that updates only when React would 50 | update the value being tracked. This approach should be concurrent-safe and 51 | more reliable for rendering. 52 | 53 | ## v0.3.0 54 | 55 | ### Breaking Changes 56 | 57 | - Simplified build tooling which removed some polyfills for older browsers 58 | - React v18+ is now a peer dependency 59 | - `useUpdateEffect` has been removed, as it will not work with React v18's 60 | concurrent features or in `` 61 | - `useEventListener` has changed in several respects: 62 | 63 | - The hook signature now has two overloads. If the first argument is a ref, 64 | its value will be used as the element to which the listener is attached. 65 | Otherwise, the first argument is the event type and the listener is attached 66 | to the `window` object. 67 | ```ts 68 | function useEventListener(type, listener, options) {} 69 | function useEventListener(elementRef, type, listener, options) {} 70 | ``` 71 | - The listener function is no longer saved in a ref internally, which means it 72 | will be attached/removed on every render unless you memoize it with 73 | `useCallback` 74 | - `useLayoutEventListener` and `useEventListenerLayoutEffect` have been 75 | removed. The options in `useEventListener` can now accept an `effectHook` 76 | option which accepts either `useEffect` or `useLayoutEffect` to control the 77 | phase in which the listener is attached and removed. 78 | 79 | - `useStableCallback` now updates the callback in `useLayoutEffect` instead of `useEffect` by default 80 | - The `history` array returned from `useStateWithHistory` is now stateful 81 | instead of a ref. This is more aligned with the React mental model when 82 | provided as a dependency, and it prevents issues with stale closures. 83 | - Removed `use-media` module. `useMatchMedia` should be imported from `use-match-media` instead 84 | - All default exports have been removed in favor of named exports 85 | - `useMatchMediaLayoutEffect` has been removed. The options in `useMatchMedia` 86 | can now accept an `effectHook` option which accepts either `useEffect` or 87 | `useLayoutEffect` to control the phase in which the listener is attached and 88 | removed. 89 | 90 | ### Features 91 | 92 | - Added `useIsHydrated` hook 93 | - `useEventListener` now supports `signal` option 94 | 95 | ### Bug-fixes 96 | 97 | - Memoize and stabilize `useForceUpdate` setter 98 | 99 | ### Other Changes 100 | 101 | - Deprecated `usePrevious`, you probably shouldn't use it 102 | 103 | ## v0.2.1 104 | 105 | - `useEventListener`: Add support for options parameter 106 | 107 | ## v0.2.0 108 | 109 | - Moved to the scope `@chance`; Install future versions with `npm i @chance/hooks` 110 | - Renamed `useMedia` to `useMatchMedia` and `useLayoutMedia` to `useMatchMediaLayoutEffect` 111 | - Renamed `useLayoutEventListener` to `useEventListenerLayoutEffect` 112 | - Added `useStableCallback`, `usePrefersReducedMotion`, `useIsomorphicLayoutEffect` 113 | 114 | ## v0.1.1 115 | 116 | - Made all types more readable 117 | - Export `PromiseStates` enum in ["usePromise"](docs/modules/_use_promise_.md) 118 | 119 | ## v0.1.0 120 | 121 | - Initial package released 122 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022-2023, Chance Strickland 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `@chance/hooks` ↪️ 2 | 3 | Handy React hooks I find myself using regularly. 4 | 5 | ```sh 6 | pnpm i @chance/hooks 7 | ``` 8 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | **@chance/hooks** • [**Docs**](modules.md) 2 | 3 | *** 4 | 5 | # `@chance/hooks` ↪️ 6 | 7 | Handy React hooks I find myself using regularly. 8 | 9 | ```sh 10 | pnpm i @chance/hooks 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/index/README.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../modules.md) / index 6 | 7 | # index 8 | 9 | ## References 10 | 11 | ### AssignableRef 12 | 13 | Re-exports [AssignableRef](../use-composed-refs/type-aliases/AssignableRef.md) 14 | 15 | *** 16 | 17 | ### assignRef 18 | 19 | Re-exports [assignRef](../use-composed-refs/functions/assignRef.md) 20 | 21 | *** 22 | 23 | ### composeEventHandlers 24 | 25 | Re-exports [composeEventHandlers](../use-composed-event-handlers/functions/composeEventHandlers.md) 26 | 27 | *** 28 | 29 | ### HistoryState 30 | 31 | Re-exports [HistoryState](../use-state-with-history/type-aliases/HistoryState.md) 32 | 33 | *** 34 | 35 | ### QueryObject 36 | 37 | Re-exports [QueryObject](../use-match-media/interfaces/QueryObject.md) 38 | 39 | *** 40 | 41 | ### ReactiveMap 42 | 43 | Re-exports [ReactiveMap](../use-map/interfaces/ReactiveMap.md) 44 | 45 | *** 46 | 47 | ### ReactiveSet 48 | 49 | Re-exports [ReactiveSet](../use-set/interfaces/ReactiveSet.md) 50 | 51 | *** 52 | 53 | ### useComposedEventHandlers 54 | 55 | Re-exports [useComposedEventHandlers](../use-composed-event-handlers/functions/useComposedEventHandlers.md) 56 | 57 | *** 58 | 59 | ### useComposedRefs 60 | 61 | Re-exports [useComposedRefs](../use-composed-refs/functions/useComposedRefs.md) 62 | 63 | *** 64 | 65 | ### useConstant 66 | 67 | Re-exports [useConstant](../use-constant/functions/useConstant.md) 68 | 69 | *** 70 | 71 | ### useControllableState 72 | 73 | Re-exports [useControllableState](../use-controllable-state/functions/useControllableState.md) 74 | 75 | *** 76 | 77 | ### useEffectEvent 78 | 79 | Re-exports [useEffectEvent](../use-effect-event/functions/useEffectEvent.md) 80 | 81 | *** 82 | 83 | ### useEventListener 84 | 85 | Re-exports [useEventListener](../use-event-listener/functions/useEventListener.md) 86 | 87 | *** 88 | 89 | ### UseEventListenerOptions 90 | 91 | Re-exports [UseEventListenerOptions](../use-event-listener/interfaces/UseEventListenerOptions.md) 92 | 93 | *** 94 | 95 | ### useForceUpdate 96 | 97 | Re-exports [useForceUpdate](../use-force-update/functions/useForceUpdate.md) 98 | 99 | *** 100 | 101 | ### useInterval 102 | 103 | Re-exports [useInterval](../use-interval/functions/useInterval.md) 104 | 105 | *** 106 | 107 | ### useIsHydrated 108 | 109 | Re-exports [useIsHydrated](../use-is-hydrated/functions/useIsHydrated.md) 110 | 111 | *** 112 | 113 | ### useLayoutEffect 114 | 115 | Re-exports [useLayoutEffect](../use-layout-effect/functions/useLayoutEffect.md) 116 | 117 | *** 118 | 119 | ### useMap 120 | 121 | Re-exports [useMap](../use-map/functions/useMap.md) 122 | 123 | *** 124 | 125 | ### useMatchMedia 126 | 127 | Re-exports [useMatchMedia](../use-match-media/functions/useMatchMedia.md) 128 | 129 | *** 130 | 131 | ### UseMatchMediaOptions 132 | 133 | Re-exports [UseMatchMediaOptions](../use-match-media/interfaces/UseMatchMediaOptions.md) 134 | 135 | *** 136 | 137 | ### usePrefersReducedMotion 138 | 139 | Re-exports [usePrefersReducedMotion](../use-prefers-reduced-motion/functions/usePrefersReducedMotion.md) 140 | 141 | *** 142 | 143 | ### usePromise 144 | 145 | Re-exports [usePromise](../use-promise/functions/usePromise.md) 146 | 147 | *** 148 | 149 | ### useSet 150 | 151 | Re-exports [useSet](../use-set/functions/useSet.md) 152 | 153 | *** 154 | 155 | ### useStateWithHistory 156 | 157 | Re-exports [useStateWithHistory](../use-state-with-history/functions/useStateWithHistory.md) 158 | 159 | *** 160 | 161 | ### UseStateWithHistoryOptions 162 | 163 | Re-exports [UseStateWithHistoryOptions](../use-state-with-history/interfaces/UseStateWithHistoryOptions.md) 164 | 165 | *** 166 | 167 | ### useTimeout 168 | 169 | Re-exports [useTimeout](../use-timeout/functions/useTimeout.md) 170 | -------------------------------------------------------------------------------- /docs/modules.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](README.md) • **Docs** 2 | 3 | *** 4 | 5 | # @chance/hooks 6 | 7 | ## Modules 8 | 9 | - [index](index/README.md) 10 | - [use-composed-event-handlers](use-composed-event-handlers/README.md) 11 | - [use-composed-refs](use-composed-refs/README.md) 12 | - [use-constant](use-constant/README.md) 13 | - [use-controllable-state](use-controllable-state/README.md) 14 | - [use-effect-event](use-effect-event/README.md) 15 | - [use-event-listener](use-event-listener/README.md) 16 | - [use-force-update](use-force-update/README.md) 17 | - [use-interval](use-interval/README.md) 18 | - [use-is-hydrated](use-is-hydrated/README.md) 19 | - [use-layout-effect](use-layout-effect/README.md) 20 | - [use-map](use-map/README.md) 21 | - [use-match-media](use-match-media/README.md) 22 | - [use-prefers-reduced-motion](use-prefers-reduced-motion/README.md) 23 | - [use-promise](use-promise/README.md) 24 | - [use-set](use-set/README.md) 25 | - [use-state-with-history](use-state-with-history/README.md) 26 | - [use-timeout](use-timeout/README.md) 27 | -------------------------------------------------------------------------------- /docs/use-composed-event-handlers/README.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../modules.md) / use-composed-event-handlers 6 | 7 | # use-composed-event-handlers 8 | 9 | ## Index 10 | 11 | ### Functions 12 | 13 | - [composeEventHandlers](functions/composeEventHandlers.md) 14 | - [useComposedEventHandlers](functions/useComposedEventHandlers.md) 15 | -------------------------------------------------------------------------------- /docs/use-composed-event-handlers/functions/composeEventHandlers.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-composed-event-handlers](../README.md) / composeEventHandlers 6 | 7 | # Function: composeEventHandlers() 8 | 9 | > **composeEventHandlers**\<`T`\>(...`handlers`): (`event`) => `void` 10 | 11 | ## Type Parameters 12 | 13 | • **T** *extends* `object` 14 | 15 | ## Parameters 16 | 17 | • ...**handlers**: (`undefined` \| `null` \| (`event`) => `void`)[] 18 | 19 | ## Returns 20 | 21 | `Function` 22 | 23 | ### Parameters 24 | 25 | • **event**: `T` 26 | 27 | ### Returns 28 | 29 | `void` 30 | 31 | ## Defined in 32 | 33 | [use-composed-event-handlers.ts:26](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-composed-event-handlers.ts#L26) 34 | -------------------------------------------------------------------------------- /docs/use-composed-event-handlers/functions/useComposedEventHandlers.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-composed-event-handlers](../README.md) / useComposedEventHandlers 6 | 7 | # Function: useComposedEventHandlers() 8 | 9 | > **useComposedEventHandlers**\<`T`\>(...`handlers`): (`event`) => `void` 10 | 11 | Composes multiple event handlers into a single function. Any handler can call 12 | `event.preventDefault()` to stop subsequent handlers from being called. 13 | 14 | ## Type Parameters 15 | 16 | • **T** *extends* `object` 17 | 18 | ## Parameters 19 | 20 | • ...**handlers**: (`undefined` \| `null` \| (`event`) => `void`)[] 21 | 22 | The event handlers to compose. It is generally a good idea to 23 | memoize the handlers, particularly if they are passed into a child component, 24 | used in an effect or provided to React context, as the returned function will 25 | otherwise be recreated on every render. 26 | 27 | ## Returns 28 | 29 | `Function` 30 | 31 | A function that will call each handler in the order it was provided. 32 | If any handler calls `event.preventDefault()`, the subsequent handlers will 33 | not be called. 34 | 35 | ### Parameters 36 | 37 | • **event**: `T` 38 | 39 | ### Returns 40 | 41 | `void` 42 | 43 | ## Defined in 44 | 45 | [use-composed-event-handlers.ts:16](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-composed-event-handlers.ts#L16) 46 | -------------------------------------------------------------------------------- /docs/use-composed-refs/README.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../modules.md) / use-composed-refs 6 | 7 | # use-composed-refs 8 | 9 | ## Index 10 | 11 | ### Type Aliases 12 | 13 | - [AssignableRef](type-aliases/AssignableRef.md) 14 | 15 | ### Functions 16 | 17 | - [assignRef](functions/assignRef.md) 18 | - [useComposedRefs](functions/useComposedRefs.md) 19 | -------------------------------------------------------------------------------- /docs/use-composed-refs/functions/assignRef.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-composed-refs](../README.md) / assignRef 6 | 7 | # Function: assignRef() 8 | 9 | > **assignRef**\<`RefValueType`\>(`ref`, `value`): `void` 10 | 11 | ## Type Parameters 12 | 13 | • **RefValueType** = `unknown` 14 | 15 | ## Parameters 16 | 17 | • **ref**: `undefined` \| `Ref`\<`RefValueType`\> 18 | 19 | • **value**: `RefValueType` 20 | 21 | ## Returns 22 | 23 | `void` 24 | 25 | ## Defined in 26 | 27 | [use-composed-refs.ts:39](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-composed-refs.ts#L39) 28 | -------------------------------------------------------------------------------- /docs/use-composed-refs/functions/useComposedRefs.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-composed-refs](../README.md) / useComposedRefs 6 | 7 | # Function: useComposedRefs() 8 | 9 | > **useComposedRefs**\<`RefValueType`\>(...`refs`): (`node`) => `void` 10 | 11 | Passes or assigns a value to multiple refs (typically a DOM node). Useful for 12 | dealing with components that need an explicit ref for DOM calculations but 13 | also forwards refs assigned by an app. 14 | 15 | ## Type Parameters 16 | 17 | • **RefValueType** = `unknown` 18 | 19 | ## Parameters 20 | 21 | • ...**refs**: (`undefined` \| `null` \| [`AssignableRef`](../type-aliases/AssignableRef.md)\<`RefValueType`\>)[] 22 | 23 | The refs to assign. These may be objects created with `useRef` or 24 | `createRef`, or ref callback functions. It is important that callback refs 25 | are memoized, just as they would be when assinging a ref to an element 26 | directly. 27 | 28 | ## Returns 29 | 30 | `Function` 31 | 32 | A callback ref that will assign (or call with, in the case of 33 | functions) the same value to all provided refs. 34 | 35 | ### Parameters 36 | 37 | • **node**: `RefValueType` 38 | 39 | ### Returns 40 | 41 | `void` 42 | 43 | ## Defined in 44 | 45 | [use-composed-refs.ts:15](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-composed-refs.ts#L15) 46 | -------------------------------------------------------------------------------- /docs/use-composed-refs/type-aliases/AssignableRef.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-composed-refs](../README.md) / AssignableRef 6 | 7 | # Type Alias: AssignableRef\ 8 | 9 | > **AssignableRef**\<`ValueType`\>: `object`\[`"bivarianceHack"`\] \| `React.MutableRefObject`\<`ValueType` \| `null`\> 10 | 11 | Either a React ref object created with `useRef` or `createRef`, or a ref 12 | callback function 13 | 14 | ## Type Parameters 15 | 16 | • **ValueType** 17 | 18 | ## Defined in 19 | 20 | [use-composed-refs.ts:33](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-composed-refs.ts#L33) 21 | -------------------------------------------------------------------------------- /docs/use-constant/README.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../modules.md) / use-constant 6 | 7 | # use-constant 8 | 9 | ## Index 10 | 11 | ### Functions 12 | 13 | - [useConstant](functions/useConstant.md) 14 | -------------------------------------------------------------------------------- /docs/use-constant/functions/useConstant.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-constant](../README.md) / useConstant 6 | 7 | # Function: useConstant() 8 | 9 | > **useConstant**\<`T`\>(`fn`): `T` 10 | 11 | ## Type Parameters 12 | 13 | • **T** 14 | 15 | ## Parameters 16 | 17 | • **fn** 18 | 19 | ## Returns 20 | 21 | `T` 22 | 23 | ## Defined in 24 | 25 | [use-constant.ts:3](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-constant.ts#L3) 26 | -------------------------------------------------------------------------------- /docs/use-controllable-state/README.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../modules.md) / use-controllable-state 6 | 7 | # use-controllable-state 8 | 9 | ## Index 10 | 11 | ### Functions 12 | 13 | - [useControllableState](functions/useControllableState.md) 14 | -------------------------------------------------------------------------------- /docs/use-controllable-state/functions/useControllableState.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-controllable-state](../README.md) / useControllableState 6 | 7 | # Function: useControllableState() 8 | 9 | > **useControllableState**\<`T`\>(`__namedParameters`): [`T`, `React.Dispatch`\<`React.SetStateAction`\<`T`\>\>] 10 | 11 | ## Type Parameters 12 | 13 | • **T** 14 | 15 | ## Parameters 16 | 17 | • **\_\_namedParameters** 18 | 19 | • **\_\_namedParameters.controlledValue**: `undefined` \| `T` 20 | 21 | • **\_\_namedParameters.internalState**: `T` 22 | 23 | • **\_\_namedParameters.onChange**: `undefined` \| (`value`) => `void` 24 | 25 | • **\_\_namedParameters.setInternalState**: `Dispatch`\<`SetStateAction`\<`T`\>\> 26 | 27 | ## Returns 28 | 29 | [`T`, `React.Dispatch`\<`React.SetStateAction`\<`T`\>\>] 30 | 31 | ## Defined in 32 | 33 | [use-controllable-state.ts:28](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-controllable-state.ts#L28) 34 | -------------------------------------------------------------------------------- /docs/use-effect-event/README.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../modules.md) / use-effect-event 6 | 7 | # use-effect-event 8 | 9 | ## Index 10 | 11 | ### Functions 12 | 13 | - [useEffectEvent](functions/useEffectEvent.md) 14 | -------------------------------------------------------------------------------- /docs/use-effect-event/functions/useEffectEvent.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-effect-event](../README.md) / useEffectEvent 6 | 7 | # Function: useEffectEvent() 8 | 9 | > **useEffectEvent**\<`T`\>(`callback`?): `T` 10 | 11 | Creates a stable callback function that has access to the latest state and 12 | can be used within event handlers and effect callbacks. Throws when used in 13 | the render phase. This is effectively a "polyfill" for the unreleased 14 | `useEffectEvent`, currently only available in canary and experimental builds 15 | of React. 16 | 17 | ## Type Parameters 18 | 19 | • **T** *extends* (...`args`) => `any` 20 | 21 | ## Parameters 22 | 23 | • **callback?**: `T` 24 | 25 | ## Returns 26 | 27 | `T` 28 | 29 | ## See 30 | 31 | https://react.dev/learn/separating-events-from-effects#declaring-an-effect-event 32 | 33 | ## Defined in 34 | 35 | [use-effect-event.ts:12](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-effect-event.ts#L12) 36 | -------------------------------------------------------------------------------- /docs/use-event-listener/README.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../modules.md) / use-event-listener 6 | 7 | # use-event-listener 8 | 9 | ## Index 10 | 11 | ### Interfaces 12 | 13 | - [UseEventListenerOptions](interfaces/UseEventListenerOptions.md) 14 | 15 | ### Functions 16 | 17 | - [useEventListener](functions/useEventListener.md) 18 | -------------------------------------------------------------------------------- /docs/use-event-listener/functions/useEventListener.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-event-listener](../README.md) / useEventListener 6 | 7 | # Function: useEventListener() 8 | 9 | ## useEventListener(element, type, listener, options) 10 | 11 | > **useEventListener**\<`K`\>(`element`, `type`, `listener`, `options`?): `void` 12 | 13 | A React hook to attach an event listener to a DOM node. This is most useful 14 | for very simple one-off event listeners attached without conditions. The 15 | listener is attached in `useEffect` and torn down in its cleanup phase. 16 | 17 | If you need to attach multiple related events, it's generally better to set 18 | those up in `useEffect` directly to reduce overhead and simplify. 19 | 20 | ### Type Parameters 21 | 22 | • **K** *extends* keyof `WindowEventMap` 23 | 24 | ### Parameters 25 | 26 | • **element**: `Window` \| `Nullish` \| `"window"` 27 | 28 | The node reference to which the event listener will be 29 | attached. This may take a few different shapes: 30 | - If you want to attach the event listener to the global `window` or 31 | `document` objects, this can be a string of `"window"` or `"document"` 32 | respectively. This is useful to avoid null-checking in your component so 33 | that this is safe for SSR. 34 | - A React ref object that contains the node. **You should always pass the ref 35 | itself rather than its current value.** The ref may not be attached when 36 | the effect runs, and since refs aren't stateful its value change won't 37 | re-trigger the effect, which means event listeners may never be attached. 38 | - A direct reference to a DOM node. This is useful if the node is stateful, 39 | in which case the listener will be detached and re-attached to synchronize 40 | with React state. 41 | 42 | • **type**: `K` 43 | 44 | The name of the event to listen for. 45 | 46 | • **listener**: `Nullish` \| (`evt`) => `any` 47 | 48 | The event listener callback that fires in response to the 49 | event being dispatched. 50 | 51 | **This should always be stabilized between renders.** If the listener 52 | reference can be saved without memoization (meaning that its dependencies 53 | changing do not need to detach or re-attach the event listener), wrap the 54 | listener in `useEffectEvent` to prevent attaching/detaching on every render. 55 | 56 | • **options?**: [`UseEventListenerOptions`](../interfaces/UseEventListenerOptions.md) 57 | 58 | An options object that specifies characteristics about the 59 | event listener 60 | 61 | ### Returns 62 | 63 | `void` 64 | 65 | ### Defined in 66 | 67 | [use-event-listener.ts:40](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-event-listener.ts#L40) 68 | 69 | ## useEventListener(element, type, listener, options) 70 | 71 | > **useEventListener**\<`K`\>(`element`, `type`, `listener`, `options`?): `void` 72 | 73 | ### Type Parameters 74 | 75 | • **K** *extends* keyof `DocumentEventMap` 76 | 77 | ### Parameters 78 | 79 | • **element**: `Nullish` \| `"document"` \| `Document` 80 | 81 | • **type**: `K` 82 | 83 | • **listener**: `Nullish` \| (`evt`) => `any` 84 | 85 | • **options?**: [`UseEventListenerOptions`](../interfaces/UseEventListenerOptions.md) 86 | 87 | ### Returns 88 | 89 | `void` 90 | 91 | ### Defined in 92 | 93 | [use-event-listener.ts:47](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-event-listener.ts#L47) 94 | 95 | ## useEventListener(element, type, listener, options) 96 | 97 | > **useEventListener**\<`T`, `K`\>(`element`, `type`, `listener`, `options`?): `void` 98 | 99 | ### Type Parameters 100 | 101 | • **T** *extends* `HTMLElement` 102 | 103 | • **K** *extends* keyof `HTMLElementEventMap` 104 | 105 | ### Parameters 106 | 107 | • **element**: `Nullish` \| `T` \| `RefObject`\<`T`\> 108 | 109 | • **type**: `K` 110 | 111 | • **listener**: `Nullish` \| (`evt`) => `any` 112 | 113 | • **options?**: [`UseEventListenerOptions`](../interfaces/UseEventListenerOptions.md) 114 | 115 | ### Returns 116 | 117 | `void` 118 | 119 | ### Defined in 120 | 121 | [use-event-listener.ts:54](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-event-listener.ts#L54) 122 | 123 | ## useEventListener(element, type, listener, options) 124 | 125 | > **useEventListener**\<`K`\>(`element`, `type`, `listener`, `options`?): `void` 126 | 127 | ### Type Parameters 128 | 129 | • **K** *extends* keyof `HTMLElementEventMap` 130 | 131 | ### Parameters 132 | 133 | • **element**: `Nullish` \| `HTMLElement` \| `RefObject`\<`HTMLElement`\> 134 | 135 | • **type**: `K` 136 | 137 | • **listener**: `Nullish` \| (`evt`) => `any` 138 | 139 | • **options?**: [`UseEventListenerOptions`](../interfaces/UseEventListenerOptions.md) 140 | 141 | ### Returns 142 | 143 | `void` 144 | 145 | ### Defined in 146 | 147 | [use-event-listener.ts:64](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-event-listener.ts#L64) 148 | 149 | ## useEventListener(element, type, listener, options) 150 | 151 | > **useEventListener**\<`T`, `K`\>(`element`, `type`, `listener`, `options`?): `void` 152 | 153 | ### Type Parameters 154 | 155 | • **T** *extends* `SVGElement` 156 | 157 | • **K** *extends* keyof `SVGElementEventMap` 158 | 159 | ### Parameters 160 | 161 | • **element**: `Nullish` \| `T` \| `RefObject`\<`T`\> 162 | 163 | • **type**: `K` 164 | 165 | • **listener**: `Nullish` \| (`evt`) => `any` 166 | 167 | • **options?**: [`UseEventListenerOptions`](../interfaces/UseEventListenerOptions.md) 168 | 169 | ### Returns 170 | 171 | `void` 172 | 173 | ### Defined in 174 | 175 | [use-event-listener.ts:71](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-event-listener.ts#L71) 176 | 177 | ## useEventListener(element, type, listener, options) 178 | 179 | > **useEventListener**\<`K`\>(`element`, `type`, `listener`, `options`?): `void` 180 | 181 | ### Type Parameters 182 | 183 | • **K** *extends* keyof `SVGElementEventMap` 184 | 185 | ### Parameters 186 | 187 | • **element**: `Nullish` \| `SVGElement` \| `RefObject`\<`SVGElement`\> 188 | 189 | • **type**: `K` 190 | 191 | • **listener**: `Nullish` \| (`evt`) => `any` 192 | 193 | • **options?**: [`UseEventListenerOptions`](../interfaces/UseEventListenerOptions.md) 194 | 195 | ### Returns 196 | 197 | `void` 198 | 199 | ### Defined in 200 | 201 | [use-event-listener.ts:81](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-event-listener.ts#L81) 202 | 203 | ## useEventListener(element, type, listener, options) 204 | 205 | > **useEventListener**\<`T`, `K`\>(`element`, `type`, `listener`, `options`?): `void` 206 | 207 | ### Type Parameters 208 | 209 | • **T** *extends* `Element` 210 | 211 | • **K** *extends* keyof `ElementEventMap` 212 | 213 | ### Parameters 214 | 215 | • **element**: `Nullish` \| `T` \| `RefObject`\<`T`\> 216 | 217 | • **type**: `K` 218 | 219 | • **listener**: `Nullish` \| (`evt`) => `any` 220 | 221 | • **options?**: [`UseEventListenerOptions`](../interfaces/UseEventListenerOptions.md) 222 | 223 | ### Returns 224 | 225 | `void` 226 | 227 | ### Defined in 228 | 229 | [use-event-listener.ts:88](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-event-listener.ts#L88) 230 | 231 | ## useEventListener(element, type, listener, options) 232 | 233 | > **useEventListener**\<`K`\>(`element`, `type`, `listener`, `options`?): `void` 234 | 235 | ### Type Parameters 236 | 237 | • **K** *extends* keyof `ElementEventMap` 238 | 239 | ### Parameters 240 | 241 | • **element**: `Nullish` \| `Element` \| `RefObject`\<`Element`\> 242 | 243 | • **type**: `K` 244 | 245 | • **listener**: `Nullish` \| (`evt`) => `any` 246 | 247 | • **options?**: [`UseEventListenerOptions`](../interfaces/UseEventListenerOptions.md) 248 | 249 | ### Returns 250 | 251 | `void` 252 | 253 | ### Defined in 254 | 255 | [use-event-listener.ts:98](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-event-listener.ts#L98) 256 | -------------------------------------------------------------------------------- /docs/use-event-listener/interfaces/UseEventListenerOptions.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-event-listener](../README.md) / UseEventListenerOptions 6 | 7 | # Interface: UseEventListenerOptions 8 | 9 | An object that specifies characteristics about an event listener. 10 | 11 | ## See 12 | 13 | [MDN: `addEventListener` options](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options). 14 | 15 | ## Properties 16 | 17 | ### capture? 18 | 19 | > `optional` **capture**: `boolean` 20 | 21 | A boolean value indicating that events of this type will be dispatched to 22 | the registered `listener` before being dispatched to any `EventTarget` 23 | beneath it in the DOM tree. If not specified, defaults to `false`. 24 | 25 | #### Defined in 26 | 27 | [use-event-listener.ts:190](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-event-listener.ts#L190) 28 | 29 | *** 30 | 31 | ### once? 32 | 33 | > `optional` **once**: `boolean` 34 | 35 | A boolean value indicating that the `listener` should be invoked at most 36 | once after being added. If `true`, the `listener` would be automatically 37 | removed when invoked. If not specified, defaults to `false`. 38 | 39 | #### Defined in 40 | 41 | [use-event-listener.ts:196](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-event-listener.ts#L196) 42 | 43 | *** 44 | 45 | ### passive? 46 | 47 | > `optional` **passive**: `boolean` 48 | 49 | A boolean value that, if `true`, indicates that the function specified by 50 | `listener` will never call `preventDefault()`. If a passive listener does 51 | call `preventDefault()`, the user agent will do nothing other than generate 52 | a console warning. If not specified, defaults to `false`. 53 | 54 | #### Defined in 55 | 56 | [use-event-listener.ts:203](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-event-listener.ts#L203) 57 | 58 | *** 59 | 60 | ### signal? 61 | 62 | > `optional` **signal**: `AbortSignal` 63 | 64 | An `AbortSignal`. The `listener` will be removed when the given 65 | `AbortSignal` object's `abort()` method is called. If not specified, no 66 | `AbortSignal` is associated with the `listener`. 67 | 68 | #### Defined in 69 | 70 | [use-event-listener.ts:209](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-event-listener.ts#L209) 71 | 72 | *** 73 | 74 | ### skip? 75 | 76 | > `optional` **skip**: `boolean` 77 | 78 | A boolean value indicating whether or not the event listener should not be 79 | attached under certain conditions. 80 | 81 | #### Defined in 82 | 83 | [use-event-listener.ts:214](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-event-listener.ts#L214) 84 | -------------------------------------------------------------------------------- /docs/use-force-update/README.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../modules.md) / use-force-update 6 | 7 | # use-force-update 8 | 9 | ## Index 10 | 11 | ### Functions 12 | 13 | - [useForceUpdate](functions/useForceUpdate.md) 14 | -------------------------------------------------------------------------------- /docs/use-force-update/functions/useForceUpdate.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-force-update](../README.md) / useForceUpdate 6 | 7 | # Function: useForceUpdate() 8 | 9 | > **useForceUpdate**(): () => `void` 10 | 11 | ## Returns 12 | 13 | `Function` 14 | 15 | ### Returns 16 | 17 | `void` 18 | 19 | ## Defined in 20 | 21 | [use-force-update.ts:3](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-force-update.ts#L3) 22 | -------------------------------------------------------------------------------- /docs/use-interval/README.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../modules.md) / use-interval 6 | 7 | # use-interval 8 | 9 | ## Index 10 | 11 | ### Functions 12 | 13 | - [useInterval](functions/useInterval.md) 14 | -------------------------------------------------------------------------------- /docs/use-interval/functions/useInterval.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-interval](../README.md) / useInterval 6 | 7 | # Function: useInterval() 8 | 9 | > **useInterval**(`callback`, `interval`?): `void` 10 | 11 | Repeatedly executes a function at a set interval. 12 | 13 | ## Parameters 14 | 15 | • **callback** 16 | 17 | A function to be executed at each interval 18 | 19 | • **interval?**: `null` \| `number` 20 | 21 | The interval in milliseconds. If this parameter is `null` or 22 | `undefined` the timer will be canceled. 23 | 24 | ## Returns 25 | 26 | `void` 27 | 28 | ## Defined in 29 | 30 | [use-interval.ts:10](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-interval.ts#L10) 31 | -------------------------------------------------------------------------------- /docs/use-is-hydrated/README.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../modules.md) / use-is-hydrated 6 | 7 | # use-is-hydrated 8 | 9 | ## Index 10 | 11 | ### Functions 12 | 13 | - [useIsHydrated](functions/useIsHydrated.md) 14 | -------------------------------------------------------------------------------- /docs/use-is-hydrated/functions/useIsHydrated.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-is-hydrated](../README.md) / useIsHydrated 6 | 7 | # Function: useIsHydrated() 8 | 9 | > **useIsHydrated**(): `boolean` 10 | 11 | Determines whether or not the component tree has been hydrated. 12 | 13 | ## Returns 14 | 15 | `boolean` 16 | 17 | Whether or not the component tree has been hydrated 18 | 19 | ## Defined in 20 | 21 | [use-is-hydrated.ts:10](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-is-hydrated.ts#L10) 22 | -------------------------------------------------------------------------------- /docs/use-layout-effect/README.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../modules.md) / use-layout-effect 6 | 7 | # use-layout-effect 8 | 9 | ## Index 10 | 11 | ### Functions 12 | 13 | - [useLayoutEffect](functions/useLayoutEffect.md) 14 | -------------------------------------------------------------------------------- /docs/use-layout-effect/functions/useLayoutEffect.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-layout-effect](../README.md) / useLayoutEffect 6 | 7 | # Function: useLayoutEffect() 8 | 9 | > **useLayoutEffect**(`effect`, `deps`?): `void` 10 | 11 | Same as React's `useLayoutEffect` but without the annoying hydration warning 12 | when called on the server. 13 | 14 | Yes, the warning is there for a reason and useful and blah blah blah, but 15 | sometimes we know better than the computer. 16 | 17 | See: https://reactjs.org/docs/hooks-reference.html#uselayouteffect 18 | 19 | ## Parameters 20 | 21 | • **effect**: `EffectCallback` 22 | 23 | • **deps?**: `DependencyList` 24 | 25 | ## Returns 26 | 27 | `void` 28 | 29 | ## Defined in 30 | 31 | [use-layout-effect.ts:24](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-layout-effect.ts#L24) 32 | -------------------------------------------------------------------------------- /docs/use-map/README.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../modules.md) / use-map 6 | 7 | # use-map 8 | 9 | ## Index 10 | 11 | ### Interfaces 12 | 13 | - [ReactiveMap](interfaces/ReactiveMap.md) 14 | 15 | ### Functions 16 | 17 | - [useMap](functions/useMap.md) 18 | -------------------------------------------------------------------------------- /docs/use-map/functions/useMap.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-map](../README.md) / useMap 6 | 7 | # Function: useMap() 8 | 9 | > **useMap**\<`K`, `V`\>(`initialEntries`?): [`ReactiveMap`](../interfaces/ReactiveMap.md)\<`K`, `V`\> 10 | 11 | ## Type Parameters 12 | 13 | • **K** = `unknown` 14 | 15 | • **V** = `unknown` 16 | 17 | ## Parameters 18 | 19 | • **initialEntries?**: `null` \| readonly readonly [`K`, `V`][] 20 | 21 | ## Returns 22 | 23 | [`ReactiveMap`](../interfaces/ReactiveMap.md)\<`K`, `V`\> 24 | 25 | ## Defined in 26 | 27 | [use-map.ts:4](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-map.ts#L4) 28 | -------------------------------------------------------------------------------- /docs/use-map/interfaces/ReactiveMap.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-map](../README.md) / ReactiveMap 6 | 7 | # Interface: ReactiveMap\ 8 | 9 | ## Type Parameters 10 | 11 | • **K** 12 | 13 | • **V** 14 | 15 | ## Properties 16 | 17 | ### raw 18 | 19 | > **raw**: `Map`\<`K`, `V`\> 20 | 21 | #### Defined in 22 | 23 | [use-map.ts:69](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-map.ts#L69) 24 | 25 | *** 26 | 27 | ### size 28 | 29 | > `readonly` **size**: `number` 30 | 31 | #### Defined in 32 | 33 | [use-map.ts:79](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-map.ts#L79) 34 | 35 | ## Methods 36 | 37 | ### clear() 38 | 39 | > **clear**(): `void` 40 | 41 | #### Returns 42 | 43 | `void` 44 | 45 | #### Defined in 46 | 47 | [use-map.ts:70](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-map.ts#L70) 48 | 49 | *** 50 | 51 | ### delete() 52 | 53 | > **delete**(`key`): `void` 54 | 55 | #### Parameters 56 | 57 | • **key**: `K` 58 | 59 | #### Returns 60 | 61 | `void` 62 | 63 | #### Defined in 64 | 65 | [use-map.ts:71](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-map.ts#L71) 66 | 67 | *** 68 | 69 | ### get() 70 | 71 | > **get**(`key`): `undefined` \| `V` 72 | 73 | #### Parameters 74 | 75 | • **key**: `K` 76 | 77 | #### Returns 78 | 79 | `undefined` \| `V` 80 | 81 | #### Defined in 82 | 83 | [use-map.ts:72](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-map.ts#L72) 84 | 85 | *** 86 | 87 | ### has() 88 | 89 | > **has**(`key`): `boolean` 90 | 91 | #### Parameters 92 | 93 | • **key**: `K` 94 | 95 | #### Returns 96 | 97 | `boolean` 98 | 99 | #### Defined in 100 | 101 | [use-map.ts:73](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-map.ts#L73) 102 | 103 | *** 104 | 105 | ### map() 106 | 107 | > **map**\<`U`\>(`callbackFn`): `U`[] 108 | 109 | #### Type Parameters 110 | 111 | • **U** 112 | 113 | #### Parameters 114 | 115 | • **callbackFn** 116 | 117 | #### Returns 118 | 119 | `U`[] 120 | 121 | #### Defined in 122 | 123 | [use-map.ts:78](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-map.ts#L78) 124 | 125 | *** 126 | 127 | ### set() 128 | 129 | > **set**(`key`, `action`): `void` 130 | 131 | #### Parameters 132 | 133 | • **key**: `K` 134 | 135 | • **action**: `V` \| (`prevValue`, `prevState`) => `V` 136 | 137 | #### Returns 138 | 139 | `void` 140 | 141 | #### Defined in 142 | 143 | [use-map.ts:74](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-map.ts#L74) 144 | -------------------------------------------------------------------------------- /docs/use-match-media/README.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../modules.md) / use-match-media 6 | 7 | # use-match-media 8 | 9 | ## Index 10 | 11 | ### Interfaces 12 | 13 | - [QueryObject](interfaces/QueryObject.md) 14 | - [UseMatchMediaOptions](interfaces/UseMatchMediaOptions.md) 15 | 16 | ### Functions 17 | 18 | - [useMatchMedia](functions/useMatchMedia.md) 19 | -------------------------------------------------------------------------------- /docs/use-match-media/functions/useMatchMedia.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-match-media](../README.md) / useMatchMedia 6 | 7 | # Function: useMatchMedia() 8 | 9 | ## useMatchMedia(rawQuery, options) 10 | 11 | > **useMatchMedia**(`rawQuery`, `options`?): `boolean` 12 | 13 | Returns whether or not a CSS media query matches. 14 | 15 | ### Parameters 16 | 17 | • **rawQuery**: `string` \| [`QueryObject`](../interfaces/QueryObject.md) \| [`QueryObject`](../interfaces/QueryObject.md)[] 18 | 19 | A string, object or array of objects representing CSS media 20 | queries 21 | 22 | • **options?**: [`UseMatchMediaOptions`](../interfaces/UseMatchMediaOptions.md) 23 | 24 | ### Returns 25 | 26 | `boolean` 27 | 28 | ### Defined in 29 | 30 | [use-match-media.ts:12](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-match-media.ts#L12) 31 | 32 | ## useMatchMedia(rawQuery, defaultState, options) 33 | 34 | > **useMatchMedia**(`rawQuery`, `defaultState`, `options`?): `boolean` 35 | 36 | Returns whether or not a CSS media query matches. 37 | 38 | ### Parameters 39 | 40 | • **rawQuery**: `string` \| [`QueryObject`](../interfaces/QueryObject.md) \| [`QueryObject`](../interfaces/QueryObject.md)[] 41 | 42 | A string, object or array of objects representing CSS media 43 | queries 44 | 45 | • **defaultState**: `boolean` 46 | 47 | The default state to return before the media query can be 48 | evaluated 49 | 50 | • **options?**: [`UseMatchMediaOptions`](../interfaces/UseMatchMediaOptions.md) 51 | 52 | ### Returns 53 | 54 | `boolean` 55 | 56 | ### Defined in 57 | 58 | [use-match-media.ts:26](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-match-media.ts#L26) 59 | -------------------------------------------------------------------------------- /docs/use-match-media/interfaces/QueryObject.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-match-media](../README.md) / QueryObject 6 | 7 | # Interface: QueryObject 8 | 9 | ## Indexable 10 | 11 | \[`property`: `string`\]: `string` \| `number` \| `boolean` 12 | -------------------------------------------------------------------------------- /docs/use-match-media/interfaces/UseMatchMediaOptions.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-match-media](../README.md) / UseMatchMediaOptions 6 | 7 | # Interface: UseMatchMediaOptions 8 | 9 | ## Properties 10 | 11 | ### effectHook()? 12 | 13 | > `optional` **effectHook**: (`effect`, `deps`?) => `void` 14 | 15 | Add the listener in either `useEffect` or `useLayoutEffect`. Defaults to 16 | `useEffect`. 17 | 18 | #### Parameters 19 | 20 | • **effect**: `EffectCallback` 21 | 22 | • **deps?**: `DependencyList` 23 | 24 | #### Returns 25 | 26 | `void` 27 | 28 | #### Defined in 29 | 30 | [use-match-media.ts:76](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-match-media.ts#L76) 31 | -------------------------------------------------------------------------------- /docs/use-prefers-reduced-motion/README.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../modules.md) / use-prefers-reduced-motion 6 | 7 | # use-prefers-reduced-motion 8 | 9 | ## Index 10 | 11 | ### Functions 12 | 13 | - [usePrefersReducedMotion](functions/usePrefersReducedMotion.md) 14 | -------------------------------------------------------------------------------- /docs/use-prefers-reduced-motion/functions/usePrefersReducedMotion.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-prefers-reduced-motion](../README.md) / usePrefersReducedMotion 6 | 7 | # Function: usePrefersReducedMotion() 8 | 9 | > **usePrefersReducedMotion**(`nodeRef`): `boolean` 10 | 11 | ## Parameters 12 | 13 | • **nodeRef**: `RefObject`\<`Element`\> 14 | 15 | ## Returns 16 | 17 | `boolean` 18 | 19 | ## Defined in 20 | 21 | [use-prefers-reduced-motion.ts:6](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-prefers-reduced-motion.ts#L6) 22 | -------------------------------------------------------------------------------- /docs/use-promise/README.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../modules.md) / use-promise 6 | 7 | # use-promise 8 | 9 | ## Index 10 | 11 | ### Functions 12 | 13 | - [usePromise](functions/usePromise.md) 14 | -------------------------------------------------------------------------------- /docs/use-promise/functions/usePromise.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-promise](../README.md) / usePromise 6 | 7 | # Function: usePromise() 8 | 9 | > **usePromise**\<`ResolvedType`, `ErrorType`\>(`promise`): readonly [`null` \| `ResolvedType`, `boolean`, `null` \| `ErrorType`] 10 | 11 | ## Type Parameters 12 | 13 | • **ResolvedType** = `any` 14 | 15 | • **ErrorType** = `any` 16 | 17 | ## Parameters 18 | 19 | • **promise** 20 | 21 | ## Returns 22 | 23 | readonly [`null` \| `ResolvedType`, `boolean`, `null` \| `ErrorType`] 24 | 25 | ## Defined in 26 | 27 | [use-promise.ts:7](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-promise.ts#L7) 28 | -------------------------------------------------------------------------------- /docs/use-set/README.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../modules.md) / use-set 6 | 7 | # use-set 8 | 9 | ## Index 10 | 11 | ### Interfaces 12 | 13 | - [ReactiveSet](interfaces/ReactiveSet.md) 14 | 15 | ### Functions 16 | 17 | - [useSet](functions/useSet.md) 18 | -------------------------------------------------------------------------------- /docs/use-set/functions/useSet.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-set](../README.md) / useSet 6 | 7 | # Function: useSet() 8 | 9 | > **useSet**\<`T`\>(`initialValues`?): [`ReactiveSet`](../interfaces/ReactiveSet.md)\<`T`\> 10 | 11 | ## Type Parameters 12 | 13 | • **T** = `unknown` 14 | 15 | ## Parameters 16 | 17 | • **initialValues?**: `null` \| readonly `T`[] 18 | 19 | ## Returns 20 | 21 | [`ReactiveSet`](../interfaces/ReactiveSet.md)\<`T`\> 22 | 23 | ## Defined in 24 | 25 | [use-set.ts:4](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-set.ts#L4) 26 | -------------------------------------------------------------------------------- /docs/use-set/interfaces/ReactiveSet.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-set](../README.md) / ReactiveSet 6 | 7 | # Interface: ReactiveSet\ 8 | 9 | ## Type Parameters 10 | 11 | • **T** 12 | 13 | ## Properties 14 | 15 | ### raw 16 | 17 | > **raw**: `Set`\<`T`\> 18 | 19 | #### Defined in 20 | 21 | [use-set.ts:71](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-set.ts#L71) 22 | 23 | *** 24 | 25 | ### size 26 | 27 | > `readonly` **size**: `number` 28 | 29 | #### Defined in 30 | 31 | [use-set.ts:77](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-set.ts#L77) 32 | 33 | ## Methods 34 | 35 | ### add() 36 | 37 | > **add**(`action`): `void` 38 | 39 | #### Parameters 40 | 41 | • **action**: `T` \| (`prevState`) => `T` 42 | 43 | #### Returns 44 | 45 | `void` 46 | 47 | #### Defined in 48 | 49 | [use-set.ts:72](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-set.ts#L72) 50 | 51 | *** 52 | 53 | ### clear() 54 | 55 | > **clear**(): `void` 56 | 57 | #### Returns 58 | 59 | `void` 60 | 61 | #### Defined in 62 | 63 | [use-set.ts:73](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-set.ts#L73) 64 | 65 | *** 66 | 67 | ### delete() 68 | 69 | > **delete**(`value`): `void` 70 | 71 | #### Parameters 72 | 73 | • **value**: `T` 74 | 75 | #### Returns 76 | 77 | `void` 78 | 79 | #### Defined in 80 | 81 | [use-set.ts:74](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-set.ts#L74) 82 | 83 | *** 84 | 85 | ### has() 86 | 87 | > **has**(`value`): `boolean` 88 | 89 | #### Parameters 90 | 91 | • **value**: `T` 92 | 93 | #### Returns 94 | 95 | `boolean` 96 | 97 | #### Defined in 98 | 99 | [use-set.ts:75](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-set.ts#L75) 100 | 101 | *** 102 | 103 | ### map() 104 | 105 | > **map**\<`U`\>(`callbackFn`): `U`[] 106 | 107 | #### Type Parameters 108 | 109 | • **U** 110 | 111 | #### Parameters 112 | 113 | • **callbackFn** 114 | 115 | #### Returns 116 | 117 | `U`[] 118 | 119 | #### Defined in 120 | 121 | [use-set.ts:76](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-set.ts#L76) 122 | -------------------------------------------------------------------------------- /docs/use-state-with-history/README.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../modules.md) / use-state-with-history 6 | 7 | # use-state-with-history 8 | 9 | ## Index 10 | 11 | ### Interfaces 12 | 13 | - [UseStateWithHistoryOptions](interfaces/UseStateWithHistoryOptions.md) 14 | 15 | ### Type Aliases 16 | 17 | - [HistoryState](type-aliases/HistoryState.md) 18 | 19 | ### Functions 20 | 21 | - [useStateWithHistory](functions/useStateWithHistory.md) 22 | -------------------------------------------------------------------------------- /docs/use-state-with-history/functions/useStateWithHistory.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-state-with-history](../README.md) / useStateWithHistory 6 | 7 | # Function: useStateWithHistory() 8 | 9 | > **useStateWithHistory**\<`ValueType`\>(`initialValue`, `opts`): [`HistoryState`](../type-aliases/HistoryState.md)\<`ValueType`\> 10 | 11 | Returns a stateful value, a function to update it, and `undo`/`redo` 12 | functions to navigate through the state's history. 13 | 14 | ## Type Parameters 15 | 16 | • **ValueType** 17 | 18 | ## Parameters 19 | 20 | • **initialValue**: `ValueType` \| () => `ValueType` 21 | 22 | The initial state value or a function to initialize it 23 | 24 | • **opts**: [`UseStateWithHistoryOptions`](../interfaces/UseStateWithHistoryOptions.md) = `{}` 25 | 26 | Optional options object 27 | 28 | ## Returns 29 | 30 | [`HistoryState`](../type-aliases/HistoryState.md)\<`ValueType`\> 31 | 32 | ## Defined in 33 | 34 | [use-state-with-history.ts:14](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-state-with-history.ts#L14) 35 | -------------------------------------------------------------------------------- /docs/use-state-with-history/interfaces/UseStateWithHistoryOptions.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-state-with-history](../README.md) / UseStateWithHistoryOptions 6 | 7 | # Interface: UseStateWithHistoryOptions 8 | 9 | ## Properties 10 | 11 | ### limit? 12 | 13 | > `optional` **limit**: `number` 14 | 15 | The maximum number of entries to keep in the history 16 | 17 | #### Defined in 18 | 19 | [use-state-with-history.ts:137](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-state-with-history.ts#L137) 20 | -------------------------------------------------------------------------------- /docs/use-state-with-history/type-aliases/HistoryState.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-state-with-history](../README.md) / HistoryState 6 | 7 | # Type Alias: HistoryState\ 8 | 9 | > **HistoryState**\<`ValueType`\>: [`ValueType`, `React.Dispatch`\<`React.SetStateAction`\<`ValueType`\>\>, () => `void`, () => `void`] 10 | 11 | ## Type Parameters 12 | 13 | • **ValueType** 14 | 15 | ## Defined in 16 | 17 | [use-state-with-history.ts:126](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-state-with-history.ts#L126) 18 | -------------------------------------------------------------------------------- /docs/use-timeout/README.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../modules.md) / use-timeout 6 | 7 | # use-timeout 8 | 9 | ## Index 10 | 11 | ### Functions 12 | 13 | - [useTimeout](functions/useTimeout.md) 14 | -------------------------------------------------------------------------------- /docs/use-timeout/functions/useTimeout.md: -------------------------------------------------------------------------------- 1 | [**@chance/hooks**](../../README.md) • **Docs** 2 | 3 | *** 4 | 5 | [@chance/hooks](../../modules.md) / [use-timeout](../README.md) / useTimeout 6 | 7 | # Function: useTimeout() 8 | 9 | > **useTimeout**(`callback`, `delay`?): `void` 10 | 11 | Sets a timer which executes a function or specified piece of code once the 12 | timer expires. 13 | 14 | ## Parameters 15 | 16 | • **callback** 17 | 18 | A function to be executed after the timer expires 19 | 20 | • **delay?**: `null` \| `number` 21 | 22 | The time, in milliseconds, that the timer should wait before the 23 | specified function or code is executed. If this parameter is 24 | `null` or `undefined` the timeout will be canceled. 25 | 26 | ## Returns 27 | 28 | `void` 29 | 30 | ## Defined in 31 | 32 | [use-timeout.ts:12](https://github.com/chaance/hooks/blob/3a106812f998ae2dc116bc6963936377cd0af671/src/use-timeout.ts#L12) 33 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import * as js from "@chance/eslint"; 2 | import * as typescript from "@chance/eslint/typescript"; 3 | import * as react from "@chance/eslint/react"; 4 | import * as vitest from "@chance/eslint/vitest"; 5 | 6 | export default [ 7 | { ignores: ["dist/**/*"] }, 8 | js.config, 9 | typescript.config, 10 | react.config, 11 | vitest.config, 12 | ]; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chance/hooks", 3 | "version": "0.6.0", 4 | "description": "Handy React hooks I use a lot ↪️", 5 | "scripts": { 6 | "build": "tsup", 7 | "test": "vitest --reporter verbose", 8 | "test:coverage": "vitest run --coverage", 9 | "doc": "typedoc --plugin typedoc-plugin-markdown --out ./docs/ --exclude \"**/lib/**/*\" --exclude \"**/test/**/*\" --entryPointStrategy expand ./src", 10 | "clean": "rm -rf node_modules && rm -rf dist" 11 | }, 12 | "author": "Chance Strickland ", 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/chaance/hooks.git" 16 | }, 17 | "license": "MIT", 18 | "type": "module", 19 | "sideEffects": false, 20 | "files": [ 21 | "CHANGELOG.md", 22 | "LICENSE", 23 | "README.md", 24 | "dist", 25 | "src" 26 | ], 27 | "devDependencies": { 28 | "@chance/eslint": "1.0.0-beta.2", 29 | "@testing-library/jest-dom": "^6.5.0", 30 | "@testing-library/react": "^16.0.1", 31 | "@types/node": "^22.6.1", 32 | "@types/react": "^18.3.9", 33 | "@types/react-dom": "^18.3.0", 34 | "@vitejs/plugin-react": "^4.3.1", 35 | "eslint": "^9.11.1", 36 | "happy-dom": "^15.7.4", 37 | "prettier": "^3.3.3", 38 | "react": "^18.3.1", 39 | "react-dom": "^18.3.1", 40 | "tsup": "^8.3.0", 41 | "typedoc": "0.26.7", 42 | "typedoc-plugin-markdown": "^4.2.8", 43 | "typescript": "^5.6.2", 44 | "vite": "^5.4.7", 45 | "vitest": "^2.1.1" 46 | }, 47 | "dependencies": { 48 | "@chance/utils": "^0.1.1" 49 | }, 50 | "peerDependencies": { 51 | "react": ">=18.0", 52 | "react-dom": ">=18.0" 53 | }, 54 | "publishConfig": { 55 | "access": "public", 56 | "types": "./dist/index.d.ts", 57 | "module": "./dist/index.js", 58 | "main": "./dist/index.cjs", 59 | "exports": { 60 | ".": { 61 | "types": "./dist/index.d.ts", 62 | "import": "./dist/index.js", 63 | "require": "./dist/index.cjs", 64 | "default": "./dist/index.js" 65 | }, 66 | "./use-composed-event-handlers": { 67 | "types": "./dist/use-composed-event-handlers.d.ts", 68 | "import": "./dist/use-composed-event-handlers.js", 69 | "require": "./dist/use-composed-event-handlers.cjs", 70 | "default": "./dist/use-composed-event-handlers.js" 71 | }, 72 | "./use-composed-refs": { 73 | "types": "./dist/use-composed-refs.d.ts", 74 | "import": "./dist/use-composed-refs.js", 75 | "require": "./dist/use-composed-refs.cjs", 76 | "default": "./dist/use-composed-refs.js" 77 | }, 78 | "./use-constant": { 79 | "types": "./dist/use-constant.d.ts", 80 | "import": "./dist/use-constant.js", 81 | "require": "./dist/use-constant.cjs", 82 | "default": "./dist/use-constant.js" 83 | }, 84 | "./use-effect-event": { 85 | "types": "./dist/use-effect-event.d.ts", 86 | "import": "./dist/use-effect-event.js", 87 | "require": "./dist/use-effect-event.cjs", 88 | "default": "./dist/use-effect-event.js" 89 | }, 90 | "./use-event-listener": { 91 | "types": "./dist/use-event-listener.d.ts", 92 | "import": "./dist/use-event-listener.js", 93 | "require": "./dist/use-event-listener.cjs", 94 | "default": "./dist/use-event-listener.js" 95 | }, 96 | "./use-force-update": { 97 | "types": "./dist/use-force-update.d.ts", 98 | "import": "./dist/use-force-update.js", 99 | "require": "./dist/use-force-update.cjs", 100 | "default": "./dist/use-force-update.js" 101 | }, 102 | "./use-interval": { 103 | "types": "./dist/use-interval.d.ts", 104 | "import": "./dist/use-interval.js", 105 | "require": "./dist/use-interval.cjs", 106 | "default": "./dist/use-interval.js" 107 | }, 108 | "./use-is-hydrated": { 109 | "types": "./dist/use-is-hydrated.d.ts", 110 | "import": "./dist/use-is-hydrated.js", 111 | "require": "./dist/use-is-hydrated.cjs", 112 | "default": "./dist/use-is-hydrated.js" 113 | }, 114 | "./use-layout-effect": { 115 | "types": "./dist/use-layout-effect.d.ts", 116 | "import": "./dist/use-layout-effect.js", 117 | "require": "./dist/use-layout-effect.cjs", 118 | "default": "./dist/use-layout-effect.js" 119 | }, 120 | "./use-map": { 121 | "types": "./dist/use-map.d.ts", 122 | "import": "./dist/use-map.js", 123 | "require": "./dist/use-map.cjs", 124 | "default": "./dist/use-map.js" 125 | }, 126 | "./use-match-media": { 127 | "types": "./dist/use-match-media.d.ts", 128 | "import": "./dist/use-match-media.js", 129 | "require": "./dist/use-match-media.cjs", 130 | "default": "./dist/use-match-media.js" 131 | }, 132 | "./use-prefers-reduced-motion": { 133 | "types": "./dist/use-prefers-reduced-motion.d.ts", 134 | "import": "./dist/use-prefers-reduced-motion.js", 135 | "require": "./dist/use-prefers-reduced-motion.cjs", 136 | "default": "./dist/use-prefers-reduced-motion.js" 137 | }, 138 | "./use-promise": { 139 | "types": "./dist/use-promise.d.ts", 140 | "import": "./dist/use-promise.js", 141 | "require": "./dist/use-promise.cjs", 142 | "default": "./dist/use-promise.js" 143 | }, 144 | "./use-set": { 145 | "types": "./dist/use-set.d.ts", 146 | "import": "./dist/use-set.js", 147 | "require": "./dist/use-set.cjs", 148 | "default": "./dist/use-set.js" 149 | }, 150 | "./use-state-with-history": { 151 | "types": "./dist/use-state-with-history.d.ts", 152 | "import": "./dist/use-state-with-history.js", 153 | "require": "./dist/use-state-with-history.cjs", 154 | "default": "./dist/use-state-with-history.js" 155 | }, 156 | "./use-timeout": { 157 | "types": "./dist/use-timeout.d.ts", 158 | "import": "./dist/use-timeout.js", 159 | "require": "./dist/use-timeout.cjs", 160 | "default": "./dist/use-timeout.js" 161 | } 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | composeEventHandlers, 3 | useComposedEventHandlers, 4 | } from "./use-composed-event-handlers"; 5 | export { 6 | type AssignableRef, 7 | assignRef, 8 | useComposedRefs, 9 | } from "./use-composed-refs"; 10 | export { useConstant } from "./use-constant"; 11 | export { useControllableState } from "./use-controllable-state"; 12 | export { useEffectEvent } from "./use-effect-event"; 13 | export { 14 | type UseEventListenerOptions, 15 | useEventListener, 16 | } from "./use-event-listener"; 17 | export { useForceUpdate } from "./use-force-update"; 18 | export { useInterval } from "./use-interval"; 19 | export { useIsHydrated } from "./use-is-hydrated"; 20 | export { useLayoutEffect } from "./use-layout-effect"; 21 | export { useMap, type ReactiveMap } from "./use-map"; 22 | export { 23 | type QueryObject, 24 | type UseMatchMediaOptions, 25 | useMatchMedia, 26 | } from "./use-match-media"; 27 | export { usePrefersReducedMotion } from "./use-prefers-reduced-motion"; 28 | export { usePromise } from "./use-promise"; 29 | export { useSet, type ReactiveSet } from "./use-set"; 30 | export { 31 | type HistoryState, 32 | type UseStateWithHistoryOptions, 33 | useStateWithHistory, 34 | } from "./use-state-with-history"; 35 | export { useTimeout } from "./use-timeout"; 36 | -------------------------------------------------------------------------------- /src/lib/json2mq.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Kiran Abburi, MIT License 2 | // https://github.com/akiran/json2mq/blob/master/index.js 3 | export function json2mq(query: QueryObject | QueryObject[]) { 4 | let mq = ""; 5 | if (typeof query === "string") { 6 | return query; 7 | } 8 | // Handling array of media queries 9 | if (Array.isArray(query)) { 10 | for (let i = 0; i < query.length; i++) { 11 | const q = query[i]; 12 | mq += obj2mq(q); 13 | if (i < query.length - 1) { 14 | mq += ", "; 15 | } 16 | } 17 | return mq; 18 | } 19 | // Handling single media query 20 | return obj2mq(query); 21 | } 22 | 23 | function isDimension(feature: string) { 24 | const re = /[height|width]$/; 25 | return re.test(feature); 26 | } 27 | 28 | function obj2mq(obj: QueryObject) { 29 | let mq = ""; 30 | const features = Object.keys(obj); 31 | for (let i = 0; i < features.length; i++) { 32 | let value = obj[features[i]]; 33 | const feature = camel2hyphen(features[i]); 34 | // Add px to dimension features 35 | if (isDimension(feature) && typeof value === "number") { 36 | value = value + "px"; 37 | } 38 | if (value === true) { 39 | mq += feature; 40 | } else if (value === false) { 41 | mq += "not " + feature; 42 | } else { 43 | mq += "(" + feature + ": " + value + ")"; 44 | } 45 | if (i < features.length - 1) { 46 | mq += " and "; 47 | } 48 | } 49 | return mq; 50 | } 51 | 52 | export interface QueryObject { 53 | [property: string]: string | number | boolean; 54 | } 55 | 56 | // https://github.com/akiran/string-convert/blob/master/camel2hyphen.js 57 | // Copyright (c) 2014 Kiran Abburi, MIT License 58 | function camel2hyphen(str: string) { 59 | return str 60 | .replace(/[A-Z]/g, (match) => { 61 | return "-" + match.toLowerCase(); 62 | }) 63 | .toLowerCase(); 64 | } 65 | -------------------------------------------------------------------------------- /src/test/setup.ts: -------------------------------------------------------------------------------- 1 | // import "@testing-library/jest-dom"; 2 | export {} 3 | -------------------------------------------------------------------------------- /src/test/use-state-with-history.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { render, act } from "@testing-library/react"; 3 | import { 4 | useStateWithHistory, 5 | type HistoryState, 6 | type UseStateWithHistoryOptions, 7 | } from "../use-state-with-history"; 8 | import { it, expect, describe } from "vitest"; 9 | 10 | function setup( 11 | defaultValue: V | (() => V), 12 | opts?: UseStateWithHistoryOptions 13 | ) { 14 | let ref: { 15 | get: () => HistoryState; 16 | set: React.Dispatch>; 17 | undo(): void; 18 | redo(): void; 19 | } = {} as any; 20 | function TestComponent() { 21 | let state = useStateWithHistory(defaultValue, opts); 22 | Object.assign(ref, { 23 | get: () => state[0], 24 | set: state[1], 25 | undo: state[2], 26 | redo: state[3], 27 | }); 28 | return null; 29 | } 30 | render(); 31 | return ref; 32 | } 33 | 34 | describe("useStateWithHistory", () => { 35 | it("returns and updates state", async () => { 36 | let state = setup("one"); 37 | expect(state.get()).toBe("one"); 38 | act(() => state.set("two")); 39 | expect(state.get()).toBe("two"); 40 | }); 41 | 42 | describe("undo", () => { 43 | it("does not update state if history is empty", async () => { 44 | let state = setup("one"); 45 | expect(state.get()).toBe("one"); 46 | act(state.undo); 47 | expect(state.get()).toBe("one"); 48 | }); 49 | 50 | it("reverts state change", async () => { 51 | let state = setup("one"); 52 | act(() => state.set("two")); 53 | act(() => state.set("three")); 54 | act(state.undo); 55 | expect(state.get()).toBe("two"); 56 | }); 57 | 58 | it("reverts to first state if at the end of history stack", async () => { 59 | let state = setup("one"); 60 | act(() => state.set("two")); 61 | act(state.undo); 62 | act(state.undo); 63 | act(state.undo); 64 | act(state.undo); 65 | expect(state.get()).toBe("one"); 66 | }); 67 | 68 | it("does not update the stack if value is unchanged", async () => { 69 | let state = setup("one"); 70 | act(() => state.set("two")); 71 | act(() => state.set("two")); 72 | act(state.undo); 73 | expect(state.get()).toBe("one"); 74 | }); 75 | }); 76 | 77 | describe("redo", () => { 78 | it("does nothing if history is empty", async () => { 79 | let state = setup("one"); 80 | expect(state.get()).toBe("one"); 81 | act(state.redo); 82 | expect(state.get()).toBe("one"); 83 | }); 84 | 85 | it("reverts undo", async () => { 86 | let state = setup("one"); 87 | act(() => state.set("two")); 88 | act(() => state.set("three")); 89 | act(state.undo); 90 | act(state.redo); 91 | expect(state.get()).toBe("three"); 92 | }); 93 | 94 | it("reverts to final state if at the end of history stack", async () => { 95 | let state = setup("one"); 96 | act(() => state.set("two")); 97 | act(() => state.set("three")); 98 | act(state.undo); 99 | act(state.redo); 100 | act(state.redo); 101 | act(state.redo); 102 | expect(state.get()).toBe("three"); 103 | }); 104 | }); 105 | 106 | describe("with { limit }", () => { 107 | it("limits undo history", async () => { 108 | let state = setup("one", { limit: 2 }); 109 | act(() => state.set("two")); 110 | act(() => state.set("three")); 111 | act(() => state.set("four")); 112 | act(state.undo); 113 | act(state.undo); 114 | act(state.undo); 115 | act(state.undo); 116 | expect(state.get()).toBe("two"); 117 | }); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /src/use-composed-event-handlers.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | 3 | /** 4 | * Composes multiple event handlers into a single function. Any handler can call 5 | * `event.preventDefault()` to stop subsequent handlers from being called. 6 | * 7 | * @param handlers The event handlers to compose. It is generally a good idea to 8 | * memoize the handlers, particularly if they are passed into a child component, 9 | * used in an effect or provided to React context, as the returned function will 10 | * otherwise be recreated on every render. 11 | * 12 | * @returns A function that will call each handler in the order it was provided. 13 | * If any handler calls `event.preventDefault()`, the subsequent handlers will 14 | * not be called. 15 | */ 16 | export function useComposedEventHandlers< 17 | T extends { defaultPrevented: boolean }, 18 | >(...handlers: Array<((event: T) => void) | null | undefined>) { 19 | return useMemo( 20 | () => composeEventHandlers(...handlers), 21 | // eslint-disable-next-line react-hooks/exhaustive-deps 22 | [...handlers], 23 | ); 24 | } 25 | 26 | export function composeEventHandlers( 27 | ...handlers: Array<((event: T) => void) | null | undefined> 28 | ) { 29 | return (event: T) => { 30 | let previousHandler: (typeof handlers)[number]; 31 | for (const handler of handlers) { 32 | previousHandler?.(event); 33 | if (!event.defaultPrevented) { 34 | handler?.(event); 35 | } 36 | previousHandler = handler; 37 | } 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /src/use-composed-refs.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from "react"; 2 | 3 | /** 4 | * Passes or assigns a value to multiple refs (typically a DOM node). Useful for 5 | * dealing with components that need an explicit ref for DOM calculations but 6 | * also forwards refs assigned by an app. 7 | * 8 | * @param refs The refs to assign. These may be objects created with `useRef` or 9 | * `createRef`, or ref callback functions. It is important that callback refs 10 | * are memoized, just as they would be when assinging a ref to an element 11 | * directly. 12 | * @returns A callback ref that will assign (or call with, in the case of 13 | * functions) the same value to all provided refs. 14 | */ 15 | export function useComposedRefs( 16 | ...refs: (AssignableRef | null | undefined)[] 17 | ) { 18 | return useCallback((node: RefValueType) => { 19 | for (const ref of refs) { 20 | assignRef(ref, node); 21 | } 22 | // IMPORTANT: We always expect refs to persist between renders so we can 23 | // ignore the lint rule in this case. Never ever pass anything other than an 24 | // actual React ref as an argument to useComposedRefs 25 | // eslint-disable-next-line react-hooks/exhaustive-deps 26 | }, refs); 27 | } 28 | 29 | /** 30 | * Either a React ref object created with `useRef` or `createRef`, or a ref 31 | * callback function 32 | */ 33 | export type AssignableRef = 34 | | { 35 | bivarianceHack(instance: ValueType | null): void; 36 | }["bivarianceHack"] 37 | | React.MutableRefObject; 38 | 39 | export function assignRef( 40 | ref: React.Ref | null | undefined, 41 | value: RefValueType, 42 | ) { 43 | if (ref == null) return; 44 | if (typeof ref === "function") { 45 | ref(value); 46 | } else { 47 | try { 48 | (ref as React.MutableRefObject).current = value; 49 | } catch { 50 | console.warn( 51 | `Cannot assign value "${value}" to ref "${ref}". This is likely a bug. Make sure refs are passed as stable callback functions or mutable ref objects. String refs are not supported.`, 52 | ); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/use-constant.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | 3 | export function useConstant(fn: () => T): T { 4 | let ref = useRef<{ v: T }>(); 5 | if (!ref.current) { 6 | ref.current = { v: fn() }; 7 | } 8 | return ref.current.v; 9 | } 10 | -------------------------------------------------------------------------------- /src/use-controllable-state.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { useEffectEvent } from "./use-effect-event"; 3 | import { isFunction } from "@chance/utils"; 4 | 5 | function useControllableStateWarning(controlledValue: unknown) { 6 | const warned = React.useRef(false); 7 | const wasControlled = React.useRef(controlledValue !== undefined); 8 | const isControlled = controlledValue !== undefined; 9 | React.useEffect(() => { 10 | if (warned.current) { 11 | return; 12 | } 13 | const docsUrl = "https://reactjs.org/link/controlled-components"; 14 | if (wasControlled.current && !isControlled) { 15 | warned.current = true; 16 | console.warn( 17 | `Warning: A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: ${docsUrl}`, 18 | ); 19 | } else if (!wasControlled.current && isControlled) { 20 | warned.current = true; 21 | console.warn( 22 | `Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: ${docsUrl}`, 23 | ); 24 | } 25 | }, [isControlled]); 26 | } 27 | 28 | export function useControllableState({ 29 | controlledValue, 30 | internalState, 31 | onChange, 32 | setInternalState, 33 | }: { 34 | controlledValue: T | undefined; 35 | internalState: T; 36 | onChange: ((value: T) => void) | undefined; 37 | setInternalState: React.Dispatch>; 38 | }): [T, React.Dispatch>] { 39 | useControllableStateWarning(controlledValue); 40 | const isControlled = controlledValue !== undefined; 41 | const _onChange = useEffectEvent((value: T) => onChange?.(value)); 42 | const _getControlledValue = useEffectEvent(() => controlledValue); 43 | const setState: React.Dispatch> = React.useCallback( 44 | (action) => { 45 | const currentState = _getControlledValue(); 46 | if (currentState !== undefined) { 47 | const nextState = isFunction(action) ? action(currentState) : action; 48 | _onChange(nextState); 49 | } else { 50 | setInternalState(action); 51 | } 52 | }, 53 | [_getControlledValue, _onChange, setInternalState], 54 | ); 55 | return [isControlled ? controlledValue : internalState, setState]; 56 | } 57 | -------------------------------------------------------------------------------- /src/use-effect-event.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useInsertionEffect, useRef } from "react"; 2 | 3 | /** 4 | * Creates a stable callback function that has access to the latest state and 5 | * can be used within event handlers and effect callbacks. Throws when used in 6 | * the render phase. This is effectively a "polyfill" for the unreleased 7 | * `useEffectEvent`, currently only available in canary and experimental builds 8 | * of React. 9 | * 10 | * @see https://react.dev/learn/separating-events-from-effects#declaring-an-effect-event 11 | */ 12 | export function useEffectEvent any>( 13 | callback?: T, 14 | ) { 15 | const ref = useRef((): any => { 16 | throw new Error("Cannot call an event handler while rendering."); 17 | }); 18 | // According to the React docs, `useInsertionEffect` callbacks will fire 19 | // before `useEffect` and `useLayoutEffect` callbacks. Since 20 | // `useInsertionEffect` isn't something we'll use outside of *potentially* 21 | // CSS-in-JS stylesheets (ew), it works well in this context to ensure that 22 | // callbacks fired in layout effects are syncronized on time. 23 | useInsertionEffect(() => { 24 | ref.current = callback; 25 | }); 26 | return useCallback((...args) => ref.current?.(...args), []) as T; 27 | } 28 | 29 | type AnyFunction = (...args: any[]) => any; 30 | -------------------------------------------------------------------------------- /src/use-event-listener.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | 3 | // #region function overloads 4 | 5 | /** 6 | * A React hook to attach an event listener to a DOM node. This is most useful 7 | * for very simple one-off event listeners attached without conditions. The 8 | * listener is attached in `useEffect` and torn down in its cleanup phase. 9 | * 10 | * If you need to attach multiple related events, it's generally better to set 11 | * those up in `useEffect` directly to reduce overhead and simplify. 12 | * 13 | * @param element The node reference to which the event listener will be 14 | * attached. This may take a few different shapes: 15 | * - If you want to attach the event listener to the global `window` or 16 | * `document` objects, this can be a string of `"window"` or `"document"` 17 | * respectively. This is useful to avoid null-checking in your component so 18 | * that this is safe for SSR. 19 | * - A React ref object that contains the node. **You should always pass the ref 20 | * itself rather than its current value.** The ref may not be attached when 21 | * the effect runs, and since refs aren't stateful its value change won't 22 | * re-trigger the effect, which means event listeners may never be attached. 23 | * - A direct reference to a DOM node. This is useful if the node is stateful, 24 | * in which case the listener will be detached and re-attached to synchronize 25 | * with React state. 26 | * 27 | * @param type The name of the event to listen for. 28 | * 29 | * @param listener The event listener callback that fires in response to the 30 | * event being dispatched. 31 | * 32 | * **This should always be stabilized between renders.** If the listener 33 | * reference can be saved without memoization (meaning that its dependencies 34 | * changing do not need to detach or re-attach the event listener), wrap the 35 | * listener in `useEffectEvent` to prevent attaching/detaching on every render. 36 | * 37 | * @param options An options object that specifies characteristics about the 38 | * event listener 39 | */ 40 | export function useEventListener( 41 | element: "window" | Window | Nullish, 42 | type: K, 43 | listener: ((evt: WindowEventMap[K]) => any) | Nullish, 44 | options?: UseEventListenerOptions, 45 | ): void; 46 | 47 | export function useEventListener( 48 | element: "document" | Document | Nullish, 49 | type: K, 50 | listener: ((evt: DocumentEventMap[K]) => any) | Nullish, 51 | options?: UseEventListenerOptions, 52 | ): void; 53 | 54 | export function useEventListener< 55 | T extends HTMLElement, 56 | K extends keyof HTMLElementEventMap, 57 | >( 58 | element: T | RefObject | Nullish, 59 | type: K, 60 | listener: ((evt: SpecificEvent) => any) | Nullish, 61 | options?: UseEventListenerOptions, 62 | ): void; 63 | 64 | export function useEventListener( 65 | element: HTMLElement | RefObject | Nullish, 66 | type: K, 67 | listener: ((evt: HTMLElementEventMap[K]) => any) | Nullish, 68 | options?: UseEventListenerOptions, 69 | ): void; 70 | 71 | export function useEventListener< 72 | T extends SVGElement, 73 | K extends keyof SVGElementEventMap, 74 | >( 75 | element: T | RefObject | Nullish, 76 | type: K, 77 | listener: ((evt: SpecificEvent) => any) | Nullish, 78 | options?: UseEventListenerOptions, 79 | ): void; 80 | 81 | export function useEventListener( 82 | element: SVGElement | RefObject | Nullish, 83 | type: K, 84 | listener: ((evt: SVGElementEventMap[K]) => any) | Nullish, 85 | options?: UseEventListenerOptions, 86 | ): void; 87 | 88 | export function useEventListener< 89 | T extends Element, 90 | K extends keyof ElementEventMap, 91 | >( 92 | element: T | RefObject | Nullish, 93 | type: K, 94 | listener: ((evt: SpecificEvent) => any) | Nullish, 95 | options?: UseEventListenerOptions, 96 | ): void; 97 | 98 | export function useEventListener( 99 | element: Element | RefObject | Nullish, 100 | type: K, 101 | listener: ((evt: ElementEventMap[K]) => any) | Nullish, 102 | options?: UseEventListenerOptions, 103 | ): void; 104 | 105 | // #endregion 106 | 107 | export function useEventListener( 108 | elementOrRef: any, 109 | type: string, 110 | listener: ((event: Event) => any) | Nullish, 111 | options?: UseEventListenerOptions, 112 | ): void { 113 | let { 114 | capture, 115 | once, 116 | // default for `passive` is inconsistent between browsers 117 | // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#specifications 118 | passive = false, 119 | signal, 120 | skip = false, 121 | } = options ?? {}; 122 | 123 | useEffect(() => { 124 | if (!elementOrRef || !listener || skip) { 125 | return; 126 | } 127 | 128 | let element: any; 129 | if (typeof elementOrRef === "string") { 130 | if (elementOrRef === "window") { 131 | element = window; 132 | } else if (elementOrRef === "document") { 133 | element = document; 134 | } else { 135 | throw new Error( 136 | `Invalid element reference: "${elementOrRef}". ` + 137 | `Expected "window" or "document".`, 138 | ); 139 | } 140 | } else if (isReactRef(elementOrRef)) { 141 | element = elementOrRef.current; 142 | } else { 143 | element = elementOrRef; 144 | } 145 | 146 | if (!element?.addEventListener) { 147 | return; 148 | } 149 | 150 | let options = { capture, once, passive, signal }; 151 | element.addEventListener(type, listener, options); 152 | return () => { 153 | element.removeEventListener(type, listener, options); 154 | }; 155 | }, [listener, capture, elementOrRef, once, passive, signal, skip, type]); 156 | } 157 | 158 | function isReactRef(value: unknown): value is React.RefObject { 159 | const isRefMaybe = 160 | typeof value === "object" && 161 | value !== null && 162 | "current" in value && 163 | value.current !== null; 164 | if (!isRefMaybe) { 165 | return false; 166 | } 167 | // it's technically possible that a `current` value is present. ie. it's 168 | // assigned to `window` for some reason, or we're dealing with a Proxy or some 169 | // other weird shit. In this context let's just make sure this is not a DOM 170 | // node. Do not use an `instanceof` check here because it may fail for 171 | // cross-origin iframes. 172 | return !( 173 | "document" in value || 174 | "createElement" in value || 175 | "tagName" in value 176 | ); 177 | } 178 | 179 | /** 180 | * An object that specifies characteristics about an event listener. 181 | * 182 | * @see [MDN: `addEventListener` options](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options). 183 | */ 184 | export interface UseEventListenerOptions { 185 | /** 186 | * A boolean value indicating that events of this type will be dispatched to 187 | * the registered `listener` before being dispatched to any `EventTarget` 188 | * beneath it in the DOM tree. If not specified, defaults to `false`. 189 | */ 190 | capture?: boolean; 191 | /** 192 | * A boolean value indicating that the `listener` should be invoked at most 193 | * once after being added. If `true`, the `listener` would be automatically 194 | * removed when invoked. If not specified, defaults to `false`. 195 | */ 196 | once?: boolean; 197 | /** 198 | * A boolean value that, if `true`, indicates that the function specified by 199 | * `listener` will never call `preventDefault()`. If a passive listener does 200 | * call `preventDefault()`, the user agent will do nothing other than generate 201 | * a console warning. If not specified, defaults to `false`. 202 | */ 203 | passive?: boolean; 204 | /** 205 | * An `AbortSignal`. The `listener` will be removed when the given 206 | * `AbortSignal` object's `abort()` method is called. If not specified, no 207 | * `AbortSignal` is associated with the `listener`. 208 | */ 209 | signal?: AbortSignal; 210 | /** 211 | * A boolean value indicating whether or not the event listener should not be 212 | * attached under certain conditions. 213 | */ 214 | skip?: boolean; 215 | } 216 | 217 | type Nullish = null | undefined; 218 | 219 | type RefObject = 220 | | React.RefObject 221 | | React.MutableRefObject; 222 | 223 | type SpecificEvent = Omit< 224 | BaseEvent, 225 | "currentTarget" 226 | > & { 227 | currentTarget: Target; 228 | }; 229 | -------------------------------------------------------------------------------- /src/use-force-update.ts: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from "react"; 2 | 3 | export function useForceUpdate() { 4 | let [, set] = useState(() => Object.create(null)); 5 | return useCallback(() => set(Object.create(null)), []); 6 | } 7 | -------------------------------------------------------------------------------- /src/use-interval.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect } from "react"; 2 | 3 | /** 4 | * Repeatedly executes a function at a set interval. 5 | * 6 | * @param callback A function to be executed at each interval 7 | * @param interval The interval in milliseconds. If this parameter is `null` or 8 | * `undefined` the timer will be canceled. 9 | */ 10 | export function useInterval( 11 | callback: () => void, 12 | interval?: number | null | undefined, 13 | ) { 14 | const savedCallback = useRef(callback); 15 | useEffect(() => { 16 | savedCallback.current = callback; 17 | }); 18 | 19 | useEffect(() => { 20 | let tick = () => savedCallback.current?.(); 21 | if (interval != null) { 22 | let id = setInterval(tick, interval); 23 | return () => clearInterval(id); 24 | } 25 | }, [interval]); 26 | } 27 | -------------------------------------------------------------------------------- /src/use-is-hydrated.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | let isHydrating = true; 4 | 5 | /** 6 | * Determines whether or not the component tree has been hydrated. 7 | * 8 | * @returns Whether or not the component tree has been hydrated 9 | */ 10 | export function useIsHydrated() { 11 | let [isHydrated, setIsHydrated] = useState(() => !isHydrating); 12 | useEffect(() => { 13 | isHydrating = false; 14 | setIsHydrated(true); 15 | }, []); 16 | return isHydrated; 17 | } 18 | -------------------------------------------------------------------------------- /src/use-layout-effect.ts: -------------------------------------------------------------------------------- 1 | import { 2 | useLayoutEffect as react_useLayoutEffect, 3 | type EffectCallback, 4 | type DependencyList, 5 | } from "react"; 6 | 7 | const canUseDOM = !!( 8 | typeof window !== "undefined" && 9 | window.document && 10 | window.document.createElement 11 | ); 12 | 13 | const noop: (...args: any) => void = () => {}; 14 | 15 | /** 16 | * Same as React's `useLayoutEffect` but without the annoying hydration warning 17 | * when called on the server. 18 | * 19 | * Yes, the warning is there for a reason and useful and blah blah blah, but 20 | * sometimes we know better than the computer. 21 | * 22 | * See: https://reactjs.org/docs/hooks-reference.html#uselayouteffect 23 | */ 24 | export function useLayoutEffect(effect: EffectCallback, deps?: DependencyList) { 25 | if (canUseDOM) { 26 | return react_useLayoutEffect(effect, deps); 27 | } else { 28 | return noop(effect, deps); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/use-map.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useMemo, useState } from "react"; 2 | import { isFunction } from "@chance/utils"; 3 | 4 | export function useMap( 5 | initialEntries?: readonly (readonly [K, V])[] | null, 6 | ): ReactiveMap { 7 | let [map, setMap] = useState(() => new Map(initialEntries)); 8 | 9 | let clear = useCallback["clear"]>(() => { 10 | setMap((map) => { 11 | return map.size === 0 ? map : new Map(); 12 | }); 13 | }, []); 14 | 15 | let _delete = useCallback["delete"]>((key) => { 16 | setMap((map) => { 17 | if (!map.has(key)) { 18 | return map; 19 | } 20 | 21 | let copy = new Map(map); 22 | copy.delete(key); 23 | return copy; 24 | }); 25 | }, []); 26 | 27 | let get = useCallback["get"]>((key) => map.get(key), [map]); 28 | 29 | let has = useCallback["has"]>((key) => map.has(key), [map]); 30 | 31 | let set = useCallback["set"]>((key, action) => { 32 | setMap((map) => { 33 | let current = map.get(key); 34 | let next = isFunction(action) ? action(current, map) : action; 35 | if (current === next) { 36 | return map; 37 | } 38 | return new Map(map.set(key, next)); 39 | }); 40 | }, []); 41 | 42 | let _map = useCallback["map"]>( 43 | (callbackFn) => { 44 | let result: ReturnType[] = []; 45 | for (let entry of map) { 46 | result.push(callbackFn(entry, result.length, map)); 47 | } 48 | return result; 49 | }, 50 | [map], 51 | ); 52 | 53 | return useMemo(() => { 54 | return { 55 | raw: map, 56 | clear, 57 | delete: _delete, 58 | get, 59 | has, 60 | set, 61 | size: map.size, 62 | map: _map, 63 | }; 64 | // eslint-disable-next-line react-hooks/exhaustive-deps 65 | }, [map]); 66 | } 67 | 68 | export interface ReactiveMap { 69 | raw: Map; 70 | clear(): void; 71 | delete(key: K): void; 72 | get(key: K): V | undefined; 73 | has(key: K): boolean; 74 | set( 75 | key: K, 76 | action: V | ((prevValue: V | undefined, prevState: Map) => V), 77 | ): void; 78 | map(callbackFn: (entry: [K, V], index: number, map: Map) => U): U[]; 79 | readonly size: number; 80 | } 81 | -------------------------------------------------------------------------------- /src/use-match-media.ts: -------------------------------------------------------------------------------- 1 | import { useEffect as react_useEffect, useMemo, useState } from "react"; 2 | import type { EffectCallback, DependencyList } from "react"; 3 | import { json2mq } from "./lib/json2mq"; 4 | 5 | /** 6 | * Returns whether or not a CSS media query matches. 7 | * 8 | * @param rawQuery A string, object or array of objects representing CSS media 9 | * queries 10 | * @param options 11 | */ 12 | export function useMatchMedia( 13 | rawQuery: string | QueryObject | QueryObject[], 14 | options?: UseMatchMediaOptions, 15 | ): boolean; 16 | 17 | /** 18 | * Returns whether or not a CSS media query matches. 19 | * 20 | * @param rawQuery A string, object or array of objects representing CSS media 21 | * queries 22 | * @param defaultState The default state to return before the media query can be 23 | * evaluated 24 | * @param options 25 | */ 26 | export function useMatchMedia( 27 | rawQuery: string | QueryObject | QueryObject[], 28 | defaultState: boolean, 29 | options?: UseMatchMediaOptions, 30 | ): boolean; 31 | 32 | export function useMatchMedia( 33 | rawQuery: string | QueryObject | QueryObject[], 34 | optionsOrDefaultState?: boolean | UseMatchMediaOptions, 35 | options?: UseMatchMediaOptions, 36 | ): boolean { 37 | let defaultState = 38 | typeof optionsOrDefaultState === "boolean" ? optionsOrDefaultState : false; 39 | let { effectHook: useEffect = react_useEffect } = 40 | options || (optionsOrDefaultState as UseMatchMediaOptions) || {}; 41 | 42 | let [state, setState] = useState(defaultState); 43 | let query = useMemo( 44 | () => (typeof rawQuery === "object" ? json2mq(rawQuery) : rawQuery), 45 | [rawQuery], 46 | ); 47 | 48 | useEffect(() => { 49 | let current = true; 50 | let mql = window.matchMedia(query); 51 | mql.addEventListener("change", handleChange); 52 | setState(mql.matches); 53 | return () => { 54 | current = false; 55 | mql.removeEventListener("change", handleChange); 56 | }; 57 | function handleChange() { 58 | if (current) { 59 | setState(mql.matches); 60 | } 61 | } 62 | }, [query]); 63 | 64 | return state; 65 | } 66 | 67 | export interface QueryObject { 68 | [property: string]: string | number | boolean; 69 | } 70 | 71 | export interface UseMatchMediaOptions { 72 | /** 73 | * Add the listener in either `useEffect` or `useLayoutEffect`. Defaults to 74 | * `useEffect`. 75 | */ 76 | effectHook?: (effect: EffectCallback, deps?: DependencyList) => void; 77 | } 78 | -------------------------------------------------------------------------------- /src/use-prefers-reduced-motion.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | const PREFERS_REDUCED_MOTION_NO_PREF_QUERY = 4 | "(prefers-reduced-motion: no-preference)"; 5 | 6 | export function usePrefersReducedMotion(nodeRef: React.RefObject) { 7 | let [prefersReducedMotion, setPrefersReducedMotion] = useState(false); 8 | useEffect(() => { 9 | return onPrefersReducedMotionChange( 10 | setPrefersReducedMotion, 11 | nodeRef.current?.ownerDocument?.defaultView || window, 12 | ); 13 | }, [nodeRef]); 14 | return prefersReducedMotion; 15 | } 16 | 17 | function onPrefersReducedMotionChange( 18 | callback: (prefers: boolean) => any, 19 | globalWindow: Window & typeof globalThis, 20 | ) { 21 | try { 22 | let mediaQueryList = globalWindow.matchMedia( 23 | PREFERS_REDUCED_MOTION_NO_PREF_QUERY, 24 | ); 25 | mediaQueryList.addEventListener("change", listener); 26 | return () => { 27 | mediaQueryList.removeEventListener("change", listener); 28 | }; 29 | } catch (e) { 30 | return () => {}; 31 | } 32 | 33 | function listener(event: MediaQueryListEvent) { 34 | callback(!event.matches); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/use-promise.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useReducer } from "react"; 2 | 3 | const LOADING = 0; 4 | const RESOLVED = 1; 5 | const ERROR = 2; 6 | 7 | export function usePromise( 8 | promise: () => Promise, 9 | ) { 10 | let [state, dispatch] = useReducer( 11 | ( 12 | state: PromiseState, 13 | action: PromiseActions, 14 | ): PromiseState => { 15 | switch (action.type) { 16 | case LOADING: 17 | return { ...state, loading: true }; 18 | case RESOLVED: 19 | return { 20 | loading: false, 21 | response: action.response, 22 | error: null, 23 | }; 24 | case ERROR: 25 | return { 26 | loading: false, 27 | response: null, 28 | error: action.error, 29 | }; 30 | default: 31 | return state; 32 | } 33 | }, 34 | { 35 | loading: false, 36 | response: null, 37 | error: null, 38 | }, 39 | ); 40 | 41 | useEffect(() => { 42 | let isCurrent = true; 43 | dispatch({ type: LOADING }); 44 | promise() 45 | .then((response) => { 46 | if (!isCurrent) return; 47 | dispatch({ type: RESOLVED, response }); 48 | }) 49 | .catch((error: ErrorType) => { 50 | dispatch({ type: ERROR, error }); 51 | }); 52 | return () => { 53 | isCurrent = false; 54 | }; 55 | }, [promise]); 56 | 57 | return [state.response, state.loading, state.error] as const; 58 | } 59 | 60 | type PromiseState = { 61 | loading: boolean; 62 | response: null | ResolvedType; 63 | error: null | ErrorType; 64 | }; 65 | 66 | type PromiseActions = 67 | | { type: typeof LOADING } 68 | | { type: typeof RESOLVED; response: ResolvedType } 69 | | { type: typeof ERROR; error: ErrorType }; 70 | -------------------------------------------------------------------------------- /src/use-set.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useMemo, useState } from "react"; 2 | import { isFunction } from "@chance/utils"; 3 | 4 | export function useSet( 5 | initialValues?: readonly T[] | null, 6 | ): ReactiveSet { 7 | let [set, setSet] = useState(() => new Set(initialValues)); 8 | 9 | let add = useCallback["add"]>((action) => { 10 | setSet((set) => { 11 | let value = isFunction(action) ? action(set) : action; 12 | if (set.has(value)) { 13 | return set; 14 | } 15 | let copy = new Set(set); 16 | copy.add(value); 17 | return copy; 18 | }); 19 | }, []); 20 | 21 | let clear = useCallback["clear"]>(() => { 22 | setSet((set) => { 23 | return set.size === 0 ? set : new Set(); 24 | }); 25 | }, []); 26 | 27 | let _delete = useCallback["delete"]>( 28 | (value) => 29 | setSet((set) => { 30 | if (!set.has(value)) { 31 | return set; 32 | } 33 | let copy = new Set(set); 34 | copy.delete(value); 35 | return copy; 36 | }), 37 | [], 38 | ); 39 | 40 | let has = useCallback["has"]>( 41 | (value) => set.has(value), 42 | [set], 43 | ); 44 | 45 | let map = useCallback["map"]>( 46 | (callbackFn) => { 47 | let result: ReturnType[] = []; 48 | for (let value of set) { 49 | result.push(callbackFn(value, result.length, set)); 50 | } 51 | return result; 52 | }, 53 | [set], 54 | ); 55 | 56 | return useMemo(() => { 57 | return { 58 | raw: set, 59 | add, 60 | clear, 61 | delete: _delete, 62 | has, 63 | map, 64 | size: set.size, 65 | }; 66 | // eslint-disable-next-line react-hooks/exhaustive-deps 67 | }, [set]); 68 | } 69 | 70 | export interface ReactiveSet { 71 | raw: Set; 72 | add(action: T | ((prevState: Set) => T)): void; 73 | clear(): void; 74 | delete(value: T): void; 75 | has(value: T): boolean; 76 | map(callbackFn: (value: T, index: number, set: Set) => U): U[]; 77 | readonly size: number; 78 | } 79 | -------------------------------------------------------------------------------- /src/use-state-with-history.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useReducer } from "react"; 2 | 3 | const SET = 0; 4 | const UNDO = 1; 5 | const REDO = 2; 6 | 7 | /** 8 | * Returns a stateful value, a function to update it, and `undo`/`redo` 9 | * functions to navigate through the state's history. 10 | * 11 | * @param initialValue The initial state value or a function to initialize it 12 | * @param opts Optional options object 13 | */ 14 | export function useStateWithHistory( 15 | initialValue: ValueType | (() => ValueType), 16 | opts: UseStateWithHistoryOptions = {}, 17 | ): HistoryState { 18 | let { limit = -1 } = opts; 19 | let [{ history, currentIndex }, send] = useReducer< 20 | Reducer, 21 | typeof initialValue 22 | >(reducer, initialValue, (initialValue) => { 23 | let history: ValueType[] = [ 24 | typeof initialValue === "function" 25 | ? (initialValue as () => ValueType)() 26 | : initialValue, 27 | ]; 28 | return { 29 | history, 30 | currentIndex: 0, 31 | }; 32 | }); 33 | 34 | let undo = useCallback(() => { 35 | send({ type: UNDO, limit }); 36 | }, [limit]); 37 | 38 | let redo = useCallback(() => { 39 | send({ type: REDO, limit }); 40 | }, [limit]); 41 | 42 | let setValue = useCallback( 43 | (newValue: ValueType | ((oldValue: ValueType) => ValueType)) => { 44 | send({ type: SET, next: newValue, limit }); 45 | }, 46 | [limit], 47 | ); 48 | 49 | return [history[currentIndex], setValue, undo, redo]; 50 | } 51 | 52 | type Reducer = ( 53 | state: State, 54 | event: Event, 55 | ) => State; 56 | 57 | function reducer( 58 | state: State, 59 | event: Event, 60 | ): State { 61 | let limit = Number.isFinite(Number(event.limit)) 62 | ? Math.round(event.limit) 63 | : -1; 64 | switch (event.type) { 65 | case SET: { 66 | let newValue = 67 | typeof event.next === "function" 68 | ? (event.next as (val: ValueType) => ValueType)( 69 | state.history[state.currentIndex], 70 | ) 71 | : event.next; 72 | 73 | if (Object.is(newValue, state.history[state.currentIndex])) { 74 | return state; 75 | } 76 | 77 | if (limit > 0 && state.history.length > limit) { 78 | // copy history array and remove oldest entry 79 | let history = state.history.slice(1); 80 | let currentIndex = history.length - 1; 81 | return { 82 | history, 83 | currentIndex, 84 | }; 85 | } 86 | 87 | let history = [...state.history, newValue]; 88 | return { 89 | history, 90 | currentIndex: state.currentIndex + 1, 91 | }; 92 | } 93 | case UNDO: { 94 | let i = state.currentIndex; 95 | return { 96 | history: state.history, 97 | currentIndex: i > 0 ? i - 1 : i, 98 | }; 99 | } 100 | case REDO: { 101 | let i = state.currentIndex; 102 | return { 103 | history: state.history, 104 | currentIndex: i < state.history.length - 1 ? i + 1 : i, 105 | }; 106 | } 107 | default: 108 | return state; 109 | } 110 | } 111 | 112 | interface State { 113 | history: ValueType[]; 114 | currentIndex: number; 115 | } 116 | 117 | type Event = 118 | | { 119 | type: typeof SET; 120 | next: ValueType | ((oldValue: ValueType) => ValueType); 121 | limit: number; 122 | } 123 | | { type: typeof UNDO; limit: number } 124 | | { type: typeof REDO; limit: number }; 125 | 126 | export type HistoryState = [ 127 | State: ValueType, 128 | Setter: React.Dispatch>, 129 | UndoFunction: () => void, 130 | RedoFunction: () => void, 131 | ]; 132 | 133 | export interface UseStateWithHistoryOptions { 134 | /** 135 | * The maximum number of entries to keep in the history 136 | */ 137 | limit?: number; 138 | } 139 | -------------------------------------------------------------------------------- /src/use-timeout.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | 3 | /** 4 | * Sets a timer which executes a function or specified piece of code once the 5 | * timer expires. 6 | * 7 | * @param callback A function to be executed after the timer expires 8 | * @param delay The time, in milliseconds, that the timer should wait before the 9 | * specified function or code is executed. If this parameter is 10 | * `null` or `undefined` the timeout will be canceled. 11 | */ 12 | export function useTimeout( 13 | callback: () => void, 14 | delay?: number | null | undefined, 15 | ) { 16 | const savedCallback = useRef(callback); 17 | useEffect(() => { 18 | savedCallback.current = callback; 19 | }); 20 | 21 | useEffect(() => { 22 | let tick = () => savedCallback.current?.(); 23 | if (delay != null) { 24 | let id = window.setTimeout(tick, delay); 25 | return () => window.clearTimeout(id); 26 | } 27 | }, [delay]); 28 | } 29 | -------------------------------------------------------------------------------- /test/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaance/hooks/a06693f66cc89fa661e4a7f7cb8f89f71b3a9d10/test/.gitkeep -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "include": ["."], 5 | "exclude": ["node_modules", "dist"], 6 | "compilerOptions": { 7 | "lib": ["ES2022", "DOM", "DOM.Iterable"], 8 | "module": "ESNext", 9 | "target": "ES2019", 10 | "jsx": "react-jsx", 11 | "emitDeclarationOnly": true, 12 | "declaration": true, 13 | "declarationMap": true, 14 | "downlevelIteration": true, 15 | "esModuleInterop": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "inlineSources": false, 18 | "isolatedModules": true, 19 | "moduleResolution": "node", 20 | "resolveJsonModule": true, 21 | "skipLibCheck": true, 22 | "strict": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | import { defineConfig } from "tsup"; 4 | import pkgJson from "./package.json"; 5 | 6 | let { name: packageName, version: packageVersion } = pkgJson; 7 | 8 | export default defineConfig((options) => { 9 | const entry = getEntrypoints(); 10 | const external = ["react", "react-dom"]; 11 | const target = "es2020"; 12 | const banner = createBanner({ 13 | author: "Chance Strickland", 14 | creationYear: 2022, 15 | license: "MIT", 16 | packageName, 17 | version: packageVersion, 18 | }); 19 | 20 | return [ 21 | // cjs 22 | { 23 | entry, 24 | format: "cjs", 25 | sourcemap: true, 26 | external, 27 | banner: { js: banner }, 28 | target, 29 | outExtension: () => ({ js: ".cjs" }), 30 | }, 31 | 32 | // esm + d.ts 33 | { 34 | entry, 35 | format: "esm", 36 | sourcemap: true, 37 | external, 38 | banner: { js: banner }, 39 | target, 40 | outExtension: () => ({ js: ".js" }), 41 | dts: { banner }, 42 | }, 43 | ]; 44 | }); 45 | 46 | function createBanner({ 47 | packageName, 48 | version, 49 | author, 50 | license, 51 | creationYear, 52 | }: { 53 | packageName: string; 54 | version: string; 55 | author: string; 56 | license: string; 57 | creationYear: string | number; 58 | }) { 59 | let currentYear = new Date().getFullYear(); 60 | let year = 61 | currentYear === Number(creationYear) 62 | ? currentYear 63 | : `${creationYear}-${currentYear}`; 64 | 65 | return `/** 66 | * ${packageName} v${version} 67 | * 68 | * Copyright (c) ${year}, ${author} 69 | * 70 | * This source code is licensed under the ${license} license found in the 71 | * LICENSE file in the root directory of this source tree. 72 | * 73 | * @license ${license} 74 | */ 75 | `; 76 | } 77 | 78 | function getEntrypoints() { 79 | const srcDir = path.join(__dirname, "src"); 80 | return fs 81 | .readdirSync(srcDir) 82 | .map((fileOrDirectoryName) => { 83 | let filePath = path.join(srcDir, fileOrDirectoryName); 84 | if (isValidEntrypoint(filePath)) { 85 | return `src/${fileOrDirectoryName}`; 86 | } 87 | return null; 88 | }) 89 | .filter((v): v is string => v != null); 90 | } 91 | 92 | function isValidEntrypoint(filePath: string) { 93 | return ( 94 | fs.statSync(filePath).isFile() && 95 | (path.extname(filePath) === ".ts" || path.extname(filePath) === ".tsx") && 96 | !/\.test\.(ts|tsx)$/.test(filePath) 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from "@vitejs/plugin-react"; 2 | import { defineConfig, configDefaults } from "vitest/config"; 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | test: { 7 | environment: "happy-dom", 8 | setupFiles: "./src/test/setup.ts", 9 | coverage: { 10 | include: ["**/*.test.{ts,tsx,js,jsx}"], 11 | exclude: [...configDefaults.exclude], 12 | }, 13 | }, 14 | }); 15 | --------------------------------------------------------------------------------