From 5c26008d9779f9dd6186389cf74f506e24ed15cb Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Mon, 21 Aug 2023 08:16:17 -0600 Subject: [PATCH 01/80] Better support for useCopyToClipboard --- index.js | 51 +++++++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/index.js b/index.js index ef502e4..9d44174 100644 --- a/index.js +++ b/index.js @@ -122,35 +122,34 @@ export function useClickAway(cb) { return ref; } -export function useCopyToClipboard() { - const [state, setState] = React.useState({ - error: null, - text: null, - }); - - const copyToClipboard = React.useCallback(async (value) => { - if (!navigator?.clipboard) { - return setState({ - error: new Error("Clipboard not supported"), - text: null, - }); - } - - const handleSuccess = () => { - setState({ - error: null, - text: value, - }); - }; +function oldSchoolCopy(text) { + const tempTextArea = document.createElement("textarea"); + tempTextArea.value = text; + document.body.appendChild(tempTextArea); + tempTextArea.select(); + document.execCommand("copy"); + document.body.removeChild(tempTextArea); +} - const handleFailure = (e) => { - setState({ - error: e, - text: null, - }); +export function useCopyToClipboard() { + const [state, setState] = React.useState(null); + + const copyToClipboard = React.useCallback((value) => { + const handleCopy = async () => { + try { + if (navigator?.clipboard?.writeText) { + await navigator.clipboard.writeText(value); + setState(value); + } else { + throw new Error("writeText not supported"); + } + } catch (e) { + oldSchoolCopy(value); + setState(value); + } }; - navigator.clipboard.writeText(value).then(handleSuccess, handleFailure); + handleCopy(); }, []); return [state, copyToClipboard]; From 10f7de1ca35ae3f67148df71fb596d2b9435803c Mon Sep 17 00:00:00 2001 From: Benjamin Adam Date: Mon, 21 Aug 2023 14:21:45 -0700 Subject: [PATCH 02/80] 2.1.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 48532c0..3d1c161 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@uidotdev/usehooks", - "version": "2.1.0", + "version": "2.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@uidotdev/usehooks", - "version": "2.1.0", + "version": "2.1.1", "license": "MIT", "devDependencies": { "@types/react": "^18.2.20", diff --git a/package.json b/package.json index e33034d..6d8b1c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uidotdev/usehooks", - "version": "2.1.0", + "version": "2.1.1", "description": "A collection of modern, server-safe React hooks – from the ui.dev team", "type": "module", "repository": "uidotdev/usehooks", From 1dccb2c72d78bad14769caa48ca1c5e5cca6c0ab Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Tue, 22 Aug 2023 09:52:10 -0600 Subject: [PATCH 03/80] Update useHistoryState and useList --- index.js | 105 +++++++++++++++++++++++++------------------------------ 1 file changed, 47 insertions(+), 58 deletions(-) diff --git a/index.js b/index.js index 9d44174..9bb87f7 100644 --- a/index.js +++ b/index.js @@ -336,38 +336,38 @@ const initialUseHistoryStateState = { const useHistoryStateReducer = (state, action) => { const { past, present, future } = state; - switch (action.type) { - case "UNDO": - return { - past: past.slice(0, past.length - 1), - present: past[past.length - 1], - future: [present, ...future], - }; - case "REDO": - return { - past: [...past, present], - present: future[0], - future: future.slice(1), - }; - case "SET": - const { newPresent } = action; - if (action.newPresent === present) { - return state; - } + if (action.type === "UNDO") { + return { + past: past.slice(0, past.length - 1), + present: past[past.length - 1], + future: [present, ...future], + }; + } else if (action.type === "REDO") { + return { + past: [...past, present], + present: future[0], + future: future.slice(1), + }; + } else if (action.type === "SET") { + const { newPresent } = action; - return { - past: [...past, present], - present: newPresent, - future: [], - }; - case "CLEAR": - return { - ...initialUseHistoryStateState, - present: action.initialPresent, - }; - default: - throw new Error("Unsupported action type"); + if (action.newPresent === present) { + return state; + } + + return { + past: [...past, present], + present: newPresent, + future: [], + }; + } else if (action.type === "CLEAR") { + return { + ...initialState, + present: action.initialPresent, + }; + } else { + throw new Error("Unsupported action type"); } }; @@ -539,40 +539,29 @@ export function useIsFirstRender() { export function useList(defaultList = []) { const [list, setList] = React.useState(defaultList); - const methods = React.useMemo(() => { - const set = (l) => { - setList(l); - }; - - const push = (element) => { - setList((l) => [...l, element]); - }; - - const removeAt = (index) => { - setList((l) => [...l.slice(0, index), ...l.slice(index + 1)]); - }; + const set = React.useCallback((l) => { + setList(l); + }, []); - const insertAt = (index, element) => { - setList((l) => [...l.slice(0, index), element, ...l.slice(index)]); - }; + const push = React.useCallback((element) => { + setList((l) => [...l, element]); + }, []); - const updateAt = (index, element) => { - setList((l) => l.map((e, i) => (i === index ? element : e))); - }; + const removeAt = React.useCallback((index) => { + setList((l) => [...l.slice(0, index), ...l.slice(index + 1)]); + }, []); - const clear = () => setList([]); + const insertAt = React.useCallback((index, element) => { + setList((l) => [...l.slice(0, index), element, ...l.slice(index)]); + }, []); - return { - set, - push, - removeAt, - insertAt, - updateAt, - clear, - }; + const updateAt = React.useCallback((index, element) => { + setList((l) => l.map((e, i) => (i === index ? element : e))); }, []); - return [list, methods]; + const clear = React.useCallback(() => setList([]), []); + + return [list, { set, push, removeAt, insertAt, updateAt, clear }]; } export function useLockBodyScroll() { From ff59e5a434a898c8b82b6e8a1bced4d15dfa93f3 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Tue, 22 Aug 2023 09:52:50 -0600 Subject: [PATCH 04/80] Update useDefault description --- usehooks.com/src/content/hooks/useDefault.mdx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/usehooks.com/src/content/hooks/useDefault.mdx b/usehooks.com/src/content/hooks/useDefault.mdx index 6c4bad8..655ea3b 100644 --- a/usehooks.com/src/content/hooks/useDefault.mdx +++ b/usehooks.com/src/content/hooks/useDefault.mdx @@ -13,13 +13,7 @@ import HookDescription from "../../components/HookDescription.astro"; import StaticCodeContainer from "../../components/StaticCodeContainer.astro"; - The useDefault hook is useful for managing state in functional components with - default values. The hook then checks if the "state" is undefined or null. If - it is, it returns an array containing the "defaultValue" and the "setState" - function, allowing the component to set a default value and update the state - accordingly. On the other hand, if the "state" is defined, it returns an array - with the current "state" value and the "setState" function, enabling normal - state management. + The `useDefault` hook behaves similar to `useState` but with one difference – if the state of the hook is `undefined` or `null`, `useDefault` will default the state to a provided default value.
@@ -28,7 +22,7 @@ import StaticCodeContainer from "../../components/StaticCodeContainer.astro";
| Name | Type | Description | | ------------ | -------- | ----------- | - | initialValue | any | This is the initial state value provided when calling `useDefault`. | + | initialValue | any | The initial value of the state returned from `useDefault` | | defaultValue | any | The default value to be used if the state is `undefined` or `null`. |
From b8de269207756bd1a2afe48a511cb1480e000d88 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Wed, 23 Aug 2023 20:52:32 -0600 Subject: [PATCH 05/80] Remove extra code in useMouseMove --- index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/index.js b/index.js index 9bb87f7..271996b 100644 --- a/index.js +++ b/index.js @@ -756,8 +756,6 @@ export function useMouse() { const elementX = event.pageX - elementPositionX; const elementY = event.pageY - elementPositionY; - newState.elementX = elementX; - newState.elementY = elementY; newState.elementX = elementX; newState.elementY = elementY; newState.elementPositionX = elementPositionX; From 38e7d6a0e89bc73a44899241ca82b23077cfc4fb Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Thu, 24 Aug 2023 07:55:16 -0600 Subject: [PATCH 06/80] Update useNetworkState --- index.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 271996b..cb7584d 100644 --- a/index.js +++ b/index.js @@ -780,18 +780,23 @@ export function useMouse() { return [state, ref]; } -export function useNetworkState() { - const connection = +const getConnection = () => { + return ( navigator?.connection || navigator?.mozConnection || - navigator?.webkitConnection; + navigator?.webkitConnection + ); +}; +export function useNetworkState() { const cache = React.useRef({}); const subscribe = React.useCallback((callback) => { window.addEventListener("online", callback, { passive: true }); window.addEventListener("offline", callback, { passive: true }); + const connection = getConnection(); + if (connection) { connection.addEventListener("change", callback, { passive: true }); } @@ -808,6 +813,7 @@ export function useNetworkState() { const getSnapshot = () => { const online = navigator.onLine; + const connection = getConnection(); const nextState = { online, From ea634b32d7cbd8b55cd5b732c8d01155d16dc15d Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Thu, 24 Aug 2023 09:49:17 -0600 Subject: [PATCH 07/80] Remove usespeech --- index.js | 51 --------------------------------------------------- 1 file changed, 51 deletions(-) diff --git a/index.js b/index.js index cb7584d..87ac1e7 100644 --- a/index.js +++ b/index.js @@ -1107,57 +1107,6 @@ export function useSet(values) { return setRef.current; } -export function useSpeech(text, options) { - const [state, setState] = React.useState(() => { - const { lang = "default", name = "" } = options.voice || {}; - return { - isPlaying: false, - status: "init", - lang: options.lang || "default", - voiceInfo: { lang, name }, - rate: options.rate || 1, - pitch: options.pitch || 1, - volume: options.volume || 1, - }; - }); - - const optionsRef = React.useRef(options); - - React.useEffect(() => { - const handlePlay = () => { - setState((s) => { - return { ...s, isPlaying: true, status: "play" }; - }); - }; - - const handlePause = () => { - setState((s) => { - return { ...s, isPlaying: false, status: "pause" }; - }); - }; - - const handleEnd = () => { - setState((s) => { - return { ...s, isPlaying: false, status: "end" }; - }); - }; - - const utterance = new SpeechSynthesisUtterance(text); - optionsRef.current.lang && (utterance.lang = optionsRef.current.lang); - optionsRef.current.voice && (utterance.voice = optionsRef.current.voice); - utterance.rate = optionsRef.current.rate || 1; - utterance.pitch = optionsRef.current.pitch || 1; - utterance.volume = optionsRef.current.volume || 1; - utterance.onstart = handlePlay; - utterance.onpause = handlePause; - utterance.onresume = handlePlay; - utterance.onend = handleEnd; - window.speechSynthesis.speak(utterance); - }, [text]); - - return state; -} - export function useThrottle(value, interval = 500) { const [throttledValue, setThrottledValue] = React.useState(value); const lastUpdated = React.useRef(); From 90d904b217372cc6a5af45a8bc1eba226d372a61 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Thu, 24 Aug 2023 13:41:34 -0600 Subject: [PATCH 08/80] Fixes #222 --- index.d.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/index.d.ts b/index.d.ts index 8e27f47..86f9f50 100644 --- a/index.d.ts +++ b/index.d.ts @@ -123,10 +123,7 @@ declare module "@uidotdev/usehooks" { ): React.MutableRefObject; export function useCopyToClipboard(): [ - { - error: Error | null; - text: string | null; - }, + string | null, (value: string) => Promise ]; From f4c81a4953125e5693aef622a2ecb44629d1aaf4 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Thu, 24 Aug 2023 13:47:09 -0600 Subject: [PATCH 09/80] Fix useGeolocation documentation --- usehooks.com/src/content/hooks/useGeolocation.mdx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/usehooks.com/src/content/hooks/useGeolocation.mdx b/usehooks.com/src/content/hooks/useGeolocation.mdx index bf58e84..16c42ba 100644 --- a/usehooks.com/src/content/hooks/useGeolocation.mdx +++ b/usehooks.com/src/content/hooks/useGeolocation.mdx @@ -24,15 +24,10 @@ import StaticCodeContainer from "../../components/StaticCodeContainer.astro";
### Parameters -{" "} -
- | Name | Type | Description | | ------- | ------ | ----------- | | options | - object | This is an optional configuration object provided when calling - `useGeolocation`. It is used when calling - `navigator.geolocation.getCurrentPosition()` and - `navigator.geolocation.watchPosition()`. Some of the attributes it accepts are - `enableHighAccuracy`, `timeout`, and `maximumAge`. | +| Name | Type | Description | +| ------- | ------ | ----------- | +| options | object | This is an optional configuration object provided when calling `useGeolocation`. It is used when calling `navigator.geolocation.getCurrentPosition()` and `navigator.geolocation.watchPosition()`. Some of the attributes it accepts are `enableHighAccuracy`, `timeout`, and `maximumAge`. |
### Return Values From 69e15d4aeb416c31395a145a817c50f60c240fe1 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Thu, 24 Aug 2023 13:48:27 -0600 Subject: [PATCH 10/80] Fix useCopyToClipboard documentation --- usehooks.com/src/content/hooks/useCopyToClipboard.mdx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/usehooks.com/src/content/hooks/useCopyToClipboard.mdx b/usehooks.com/src/content/hooks/useCopyToClipboard.mdx index b42ad4b..555fb0c 100644 --- a/usehooks.com/src/content/hooks/useCopyToClipboard.mdx +++ b/usehooks.com/src/content/hooks/useCopyToClipboard.mdx @@ -23,14 +23,6 @@ import StaticCodeContainer from "../../components/StaticCodeContainer.astro";
- ### Parameters - -
- | Name | Type | Description | - | ----- | ------ | ---------------------------------------- | - | value | string | The value to be copied to the clipboard. | -
- ### Return Value The `useCopyToClipboard` hook returns an array with the following elements: From 8f3af9ba483a8ba7cae0090e5d92eb75cf1815cd Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Thu, 24 Aug 2023 14:42:05 -0600 Subject: [PATCH 11/80] Migrate useVisibilityChange to use uSES --- index.js | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/index.js b/index.js index 87ac1e7..0f72411 100644 --- a/index.js +++ b/index.js @@ -1144,27 +1144,30 @@ export function useToggle(initialValue) { return [on, handleToggle]; } -export function useVisibilityChange() { - const [documentVisible, setDocumentVisibility] = React.useState(true); +const useVisibilityChangeSubscribe = (callback) => { + document.addEventListener("visibilitychange", callback); - React.useEffect(() => { - const handleChange = () => { - if (document.visibilityState !== "visible") { - setDocumentVisibility(false); - } else { - setDocumentVisibility(true); - } - }; - handleChange(); + return () => { + document.removeEventListener("visibilitychange", callback); + }; +}; - document.addEventListener("visibilitychange", handleChange); +const getVisibilityChangeSnapshot = () => { + return document.visibilityState; +}; - return () => { - document.removeEventListener("visibilitychange", handleChange); - }; - }, []); +const getVisibilityChangeServerSnapshot = () => { + throw Error("useVisibilityChange is a client-only hook"); +}; + +export function useVisibilityChange() { + const visibilityState = React.useSyncExternalStore( + useVisibilityChangeSubscribe, + getVisibilityChangeSnapshot, + getVisibilityChangeServerSnapshot + ); - return documentVisible; + return visibilityState === "visible"; } export function useWindowScroll() { From 0b307fa758234d0955f7f4bee8da3d868a8b1ce1 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Fri, 25 Aug 2023 08:21:12 -0600 Subject: [PATCH 12/80] Update useContinuousRetry documentation --- usehooks.com/src/content/hooks/useContinuousRetry.mdx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/usehooks.com/src/content/hooks/useContinuousRetry.mdx b/usehooks.com/src/content/hooks/useContinuousRetry.mdx index e2549c2..9d088d6 100644 --- a/usehooks.com/src/content/hooks/useContinuousRetry.mdx +++ b/usehooks.com/src/content/hooks/useContinuousRetry.mdx @@ -27,10 +27,11 @@ import StaticCodeContainer from "../../components/StaticCodeContainer.astro"; ### Parameters
- | Name | Type | Description | - | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------- | - | callback | function | The callback function to be executed repeatedly until it returns a truthy value. | - | interval | number | (Optional) The interval in milliseconds at which the callback function is executed. Default value is 100 milliseconds. | + | Name | Type | Description | + | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------| + | callback | function | The callback function to be executed repeatedly until it returns a truthy value. | + | interval | number | (Optional) The interval in milliseconds at which the callback function is executed. Default value is 100 milliseconds. | + | options | object | (Optional) An object containing a `maxRetries` property which tells `useContinuousRetry` the maximum amount of retry attempts it should make before stopping |
### Return Value From 7f98f5332f13f7e10bc685f1f067d0761f4e3d71 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Sun, 27 Aug 2023 21:24:24 -0600 Subject: [PATCH 13/80] Add useLocalStorage to main package --- index.js | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/index.js b/index.js index 0f72411..62f2e5e 100644 --- a/index.js +++ b/index.js @@ -564,6 +564,72 @@ export function useList(defaultList = []) { return [list, { set, push, removeAt, insertAt, updateAt, clear }]; } +const dispatchStorageEvent = (key, newValue) => { + window.dispatchEvent(new StorageEvent("storage", { key, newValue })); +}; + +const setLocalStorageItem = (key, value) => { + const stringifiedValue = JSON.stringify(value); + window.localStorage.setItem(key, stringifiedValue); + dispatchStorageEvent(key, stringifiedValue); +}; + +const removeLocalStorageItem = (key) => { + window.localStorage.removeItem(key); + dispatchStorageEvent(key, null); +}; + +const getLocalStorageItem = (key) => { + return window.localStorage.getItem(key); +}; + +const useLocalStorageSubscribe = (callback) => { + window.addEventListener("storage", callback); + return () => window.removeEventListener("storage", callback); +}; + +const getLocalStorageServerSnapshot = () => { + throw Error("useLocalStorage is a client-only hook"); +}; + +export function useLocalStorage(key, initialValue) { + const getSnapshot = () => getLocalStorageItem(key); + + const store = React.useSyncExternalStore( + useLocalStorageSubscribe, + getSnapshot, + getLocalStorageServerSnapshot + ); + + const setState = React.useCallback( + (v) => { + try { + const nextState = typeof v === "function" ? v(JSON.parse(store)) : v; + + if (nextState === undefined || nextState === null) { + removeLocalStorageItem(key); + } else { + setLocalStorageItem(key, nextState); + } + } catch (e) { + console.warn(e); + } + }, + [key, store] + ); + + React.useEffect(() => { + if ( + getLocalStorageItem(key) === null && + typeof initialValue !== "undefined" + ) { + setLocalStorageItem(key, initialValue); + } + }, [key, initialValue]); + + return [store ? JSON.parse(store) : initialValue, setState]; +} + export function useLockBodyScroll() { React.useEffect(() => { const originalStyle = window.getComputedStyle(document.body).overflow; From 4988f9249c8d73115555e542ff394f8e0591436c Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Mon, 28 Aug 2023 13:13:28 -0600 Subject: [PATCH 14/80] Add useSessionStorage to main package --- index.js | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 62f2e5e..f54b0df 100644 --- a/index.js +++ b/index.js @@ -38,6 +38,10 @@ function throttle(cb, ms) { }; } +const dispatchStorageEvent = (key, newValue) => { + window.dispatchEvent(new StorageEvent("storage", { key, newValue })); +}; + export function useBattery() { const [state, setState] = React.useState({ supported: true, @@ -564,10 +568,6 @@ export function useList(defaultList = []) { return [list, { set, push, removeAt, insertAt, updateAt, clear }]; } -const dispatchStorageEvent = (key, newValue) => { - window.dispatchEvent(new StorageEvent("storage", { key, newValue })); -}; - const setLocalStorageItem = (key, value) => { const stringifiedValue = JSON.stringify(value); window.localStorage.setItem(key, stringifiedValue); @@ -1147,6 +1147,68 @@ export function useScript(src, options = {}) { return status; } +const setSessionStorageItem = (key, value) => { + const stringifiedValue = JSON.stringify(value); + window.sessionStorage.setItem(key, stringifiedValue); + dispatchStorageEvent(key, stringifiedValue); +}; + +const removeSessionStorageItem = (key) => { + window.sessionStorage.removeItem(key); + dispatchStorageEvent(key, null); +}; + +const getSessionStorageItem = (key) => { + return window.sessionStorage.getItem(key); +}; + +const useSessionStorageSubscribe = (callback) => { + window.addEventListener("storage", callback); + return () => window.removeEventListener("storage", callback); +}; + +const getSessionStorageServerSnapshot = () => { + throw Error("useSessionStorage is a client-only hook"); +}; + +export function useSessionStorage(key, initialValue) { + const getSnapshot = () => getSessionStorageItem(key); + + const store = React.useSyncExternalStore( + useSessionStorageSubscribe, + getSnapshot, + getSessionStorageServerSnapshot + ); + + const setState = React.useCallback( + (v) => { + try { + const nextState = typeof v === "function" ? v(JSON.parse(store)) : v; + + if (nextState === undefined || nextState === null) { + removeSessionStorageItem(key); + } else { + setSessionStorageItem(key, nextState); + } + } catch (e) { + console.warn(e); + } + }, + [key, store] + ); + + React.useEffect(() => { + if ( + getSessionStorageItem(key) === null && + typeof initialValue !== "undefined" + ) { + setSessionStorageItem(key, initialValue); + } + }, [key, initialValue]); + + return [store ? JSON.parse(store) : initialValue, setState]; +} + export function useSet(values) { const setRef = React.useRef(new Set(values)); const [, reRender] = React.useReducer((x) => x + 1, 0); From c065ab1f9b7cb7f6dcd6f48f2054b95d98cf55b6 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Mon, 28 Aug 2023 15:32:40 -0600 Subject: [PATCH 15/80] Update usePrevious --- index.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index f54b0df..1025a2d 100644 --- a/index.js +++ b/index.js @@ -998,14 +998,16 @@ export function usePreferredLanguage() { ); } -export function usePrevious(newValue) { - const previousRef = React.useRef(); +export function usePrevious(value) { + const [current, setCurrent] = React.useState(value); + const [previous, setPrevious] = React.useState(null); - React.useEffect(() => { - previousRef.current = newValue; - }); + if (value !== current) { + setPrevious(current); + setCurrent(value); + } - return previousRef.current; + return previous; } export function useQueue(initialValue = []) { From 78360d3a73ac3fb9f7d42113fdeb95eb3dc098d9 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Tue, 29 Aug 2023 08:14:59 -0600 Subject: [PATCH 16/80] Refactor useLongPress --- index.js | 49 +++++++++++++++++-------------------------------- 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/index.js b/index.js index 1025a2d..d6a8960 100644 --- a/index.js +++ b/index.js @@ -640,23 +640,18 @@ export function useLockBodyScroll() { }, []); } -export function useLongPress( - callback, - { threshold = 400, onStart, onFinish, onCancel } = {} -) { +export function useLongPress(callback, options = {}) { + const { threshold = 400, onStart, onFinish, onCancel } = options; const isLongPressActive = React.useRef(false); const isPressed = React.useRef(false); const timerId = React.useRef(); - const cbRef = React.useRef(callback); - React.useLayoutEffect(() => { - cbRef.current = callback; - }); - - const start = React.useCallback( - () => (event) => { - if (isPressed.current) return; + return React.useMemo(() => { + if (typeof callback !== "function") { + return {}; + } + const start = (event) => { if (!isMouseEvent(event) && !isTouchEvent(event)) return; if (onStart) { @@ -665,15 +660,12 @@ export function useLongPress( isPressed.current = true; timerId.current = setTimeout(() => { - cbRef.current(event); + callback(event); isLongPressActive.current = true; }, threshold); - }, - [onStart, threshold] - ); + }; - const cancel = React.useCallback( - () => (event) => { + const cancel = (event) => { if (!isMouseEvent(event) && !isTouchEvent(event)) return; if (isLongPressActive.current) { @@ -692,31 +684,24 @@ export function useLongPress( if (timerId.current) { window.clearTimeout(timerId.current); } - }, - [onFinish, onCancel] - ); - - return React.useMemo(() => { - if (callback === null) { - return {}; - } + }; const mouseHandlers = { - onMouseDown: start(), - onMouseUp: cancel(), - onMouseLeave: cancel(), + onMouseDown: start, + onMouseUp: cancel, + onMouseLeave: cancel, }; const touchHandlers = { - onTouchStart: start(), - onTouchEnd: cancel(), + onTouchStart: start, + onTouchEnd: cancel, }; return { ...mouseHandlers, ...touchHandlers, }; - }, [callback, cancel, start]); + }, [callback, threshold, onCancel, onFinish, onStart]); } export function useMap(initialState) { From 3529e5c0061bb673ee6acecdf235606b2eb34463 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Tue, 29 Aug 2023 10:47:28 -0600 Subject: [PATCH 17/80] Refactor useScript --- index.js | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/index.js b/index.js index d6a8960..e0e0dc1 100644 --- a/index.js +++ b/index.js @@ -1075,9 +1075,7 @@ export function useScript(src, options = {}) { const cachedScriptStatuses = React.useRef({}); React.useEffect(() => { - if (!src) { - return; - } + if (!src) return; const cachedScriptStatus = cachedScriptStatuses.current[src]; if (cachedScriptStatus === "ready" || cachedScriptStatus === "error") { @@ -1088,26 +1086,12 @@ export function useScript(src, options = {}) { let script = document.querySelector(`script[src="${src}"]`); if (script) { - setStatus( - script.getAttribute("data-status") ?? cachedScriptStatus ?? "loading" - ); + setStatus(cachedScriptStatus ?? "loading"); } else { script = document.createElement("script"); script.src = src; script.async = true; - script.setAttribute("data-status", "loading"); document.body.appendChild(script); - - const setAttributeFromEvent = (event) => { - const scriptStatus = event.type === "load" ? "ready" : "error"; - - if (script) { - script.setAttribute("data-status", scriptStatus); - } - }; - - script.addEventListener("load", setAttributeFromEvent); - script.addEventListener("error", setAttributeFromEvent); } const setStateFromEvent = (event) => { From f6c5f94f1261d6593569b7dd26d8654ab29ef514 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Tue, 29 Aug 2023 10:57:41 -0600 Subject: [PATCH 18/80] Remove experimental on useLocalStorage and useSessionStorage --- usehooks.com/src/content/hooks/useLocalStorage.mdx | 1 - usehooks.com/src/content/hooks/useSessionStorage.mdx | 1 - 2 files changed, 2 deletions(-) diff --git a/usehooks.com/src/content/hooks/useLocalStorage.mdx b/usehooks.com/src/content/hooks/useLocalStorage.mdx index f1b46f1..cdff602 100644 --- a/usehooks.com/src/content/hooks/useLocalStorage.mdx +++ b/usehooks.com/src/content/hooks/useLocalStorage.mdx @@ -1,6 +1,5 @@ --- name: useLocalStorage -experimental: true rank: 2 tagline: Store, retrieve, and synchronize data from the browser’s localStorage API with useLocalStorage sandboxId: uselocalstorage-e6v6ey diff --git a/usehooks.com/src/content/hooks/useSessionStorage.mdx b/usehooks.com/src/content/hooks/useSessionStorage.mdx index 10208c1..0405fa9 100644 --- a/usehooks.com/src/content/hooks/useSessionStorage.mdx +++ b/usehooks.com/src/content/hooks/useSessionStorage.mdx @@ -1,6 +1,5 @@ --- name: useSessionStorage -experimental: true rank: 9 tagline: Store, retrieve, and synchronize data from the browser’s session storage with useSessionStorage. sandboxId: usesessionstorage-yoodu4 From 3913248f7a7354df0aaf3a500e0cb2358a40bca3 Mon Sep 17 00:00:00 2001 From: Benjamin Adam Date: Tue, 29 Aug 2023 14:05:29 -0700 Subject: [PATCH 19/80] 2.2.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3d1c161..1175c6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@uidotdev/usehooks", - "version": "2.1.1", + "version": "2.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@uidotdev/usehooks", - "version": "2.1.1", + "version": "2.2.0", "license": "MIT", "devDependencies": { "@types/react": "^18.2.20", diff --git a/package.json b/package.json index 6d8b1c6..521d2ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uidotdev/usehooks", - "version": "2.1.1", + "version": "2.2.0", "description": "A collection of modern, server-safe React hooks – from the ui.dev team", "type": "module", "repository": "uidotdev/usehooks", From e5d445ead108b57e081fd1177f440b05ebc5d184 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Wed, 30 Aug 2023 08:57:40 -0600 Subject: [PATCH 20/80] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1bec540..48c7715 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Compatible with React v18.0.0+. - [useIsClient](https://usehooks.com/useisclient) - [useIsFirstRender](https://usehooks.com/useisfirstrender) - [useList](https://usehooks.com/uselist) +- [useLocalStorage](https://usehooks.com/uselocalstorage) - [useLockBodyScroll](https://usehooks.com/uselockbodyscroll) - [useLongPress](https://usehooks.com/uselongpress) - [useMap](https://usehooks.com/usemap) @@ -45,6 +46,7 @@ Compatible with React v18.0.0+. - [useRenderCount](https://usehooks.com/userendercount) - [useRenderInfo](https://usehooks.com/userenderinfo) - [useScript](https://usehooks.com/usescript) +- [useSessionStorage](https://usehooks.com/usesessionstorage) - [useSet](https://usehooks.com/useset) - [useThrottle](https://usehooks.com/usethrottle) - [useToggle](https://usehooks.com/usetoggle) @@ -67,9 +69,7 @@ Compatible with React v18.0.0+. - [useInterval](https://usehooks.com/useinterval) - [useIntervalWhen](https://usehooks.com/useintervalwhen) - [useKeyPress](https://usehooks.com/usekeypress) -- [useLocalStorage](https://usehooks.com/uselocalstorage) - [useLogger](https://usehooks.com/uselogger) - [usePageLeave](https://usehooks.com/usepageleave) - [useRandomInterval](https://usehooks.com/userandominterval) -- [useSessionStorage](https://usehooks.com/usesessionstorage) - [useTimeout](https://usehooks.com/usetimeout) From 4867d7b3bff7bc3655da487908e7a422e3fecc37 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Fri, 1 Sep 2023 07:34:16 -0600 Subject: [PATCH 21/80] Update useThrottle --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index e0e0dc1..ebbe0db 100644 --- a/index.js +++ b/index.js @@ -1208,12 +1208,12 @@ export function useSet(values) { export function useThrottle(value, interval = 500) { const [throttledValue, setThrottledValue] = React.useState(value); - const lastUpdated = React.useRef(); + const lastUpdated = React.useRef(null); React.useEffect(() => { const now = Date.now(); - if (now >= lastUpdated.current + interval) { + if (lastUpdated.current && now >= lastUpdated.current + interval) { lastUpdated.current = now; setThrottledValue(value); } else { From 6eba0d65135444cc33f711f7ce738b0f4f7816c2 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Fri, 1 Sep 2023 09:07:05 -0600 Subject: [PATCH 22/80] Refactor useScript --- index.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index ebbe0db..9b3a3dd 100644 --- a/index.js +++ b/index.js @@ -1085,31 +1085,27 @@ export function useScript(src, options = {}) { let script = document.querySelector(`script[src="${src}"]`); - if (script) { - setStatus(cachedScriptStatus ?? "loading"); - } else { + if (!script) { script = document.createElement("script"); script.src = src; script.async = true; document.body.appendChild(script); } - const setStateFromEvent = (event) => { + const handleScriptStatus = (event) => { const newStatus = event.type === "load" ? "ready" : "error"; setStatus(newStatus); cachedScriptStatuses.current[src] = newStatus; }; - script.addEventListener("load", setStateFromEvent); - script.addEventListener("error", setStateFromEvent); + script.addEventListener("load", handleScriptStatus); + script.addEventListener("error", handleScriptStatus); return () => { - if (script) { - script.removeEventListener("load", setStateFromEvent); - script.removeEventListener("error", setStateFromEvent); - } + script.removeEventListener("load", handleScriptStatus); + script.removeEventListener("error", handleScriptStatus); - if (script && options.removeOnUnmount) { + if (options.removeOnUnmount === true) { script.remove(); } }; From 9c6865d9abf8ce184072b634ab70b3aba50fc80e Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Fri, 1 Sep 2023 09:46:56 -0600 Subject: [PATCH 23/80] Refactor useNetworkState --- index.js | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/index.js b/index.js index 9b3a3dd..652fa5f 100644 --- a/index.js +++ b/index.js @@ -839,28 +839,32 @@ const getConnection = () => { ); }; -export function useNetworkState() { - const cache = React.useRef({}); +const useNetworkStateSubscribe = (callback) => { + window.addEventListener("online", callback, { passive: true }); + window.addEventListener("offline", callback, { passive: true }); - const subscribe = React.useCallback((callback) => { - window.addEventListener("online", callback, { passive: true }); - window.addEventListener("offline", callback, { passive: true }); + const connection = getConnection(); - const connection = getConnection(); + if (connection) { + connection.addEventListener("change", callback, { passive: true }); + } + + return () => { + window.removeEventListener("online", callback); + window.removeEventListener("offline", callback); if (connection) { - connection.addEventListener("change", callback, { passive: true }); + connection.removeEventListener("change", callback); } + }; +}; - return () => { - window.removeEventListener("online", callback); - window.removeEventListener("offline", callback); +const getNetworkStateServerSnapshot = () => { + throw Error("useNetworkState is a client-only hook"); +}; - if (connection) { - connection.removeEventListener("change", callback); - } - }; - }, []); +export function useNetworkState() { + const cache = React.useRef({}); const getSnapshot = () => { const online = navigator.onLine; @@ -884,11 +888,11 @@ export function useNetworkState() { } }; - const getServerSnapshot = () => { - throw Error("useNetworkState is a client-only hook"); - }; - - return React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); + return React.useSyncExternalStore( + useNetworkStateSubscribe, + getSnapshot, + getNetworkStateServerSnapshot + ); } export function useObjectState(initialValue) { From abecedb142724b077d8a7eb086ce5932a85bb188 Mon Sep 17 00:00:00 2001 From: Angeart Date: Sat, 2 Sep 2023 03:48:56 +0900 Subject: [PATCH 24/80] fix: return type of useQueue --- index.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/index.d.ts b/index.d.ts index 86f9f50..1dde5a9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -82,6 +82,7 @@ export type CustomQueue = { first: T | undefined; last: T | undefined; size: number; + queue: T[]; }; export type RenderInfo = { From af43c5e5acd21adb3ed41e38fa2d1266bc2e8ce4 Mon Sep 17 00:00:00 2001 From: Justin Gurtz <40037115+justin-gurtz@users.noreply.github.com> Date: Fri, 1 Sep 2023 17:35:15 -0400 Subject: [PATCH 25/80] Make ref type generic --- index.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.d.ts b/index.d.ts index 86f9f50..92ab776 100644 --- a/index.d.ts +++ b/index.d.ts @@ -165,9 +165,9 @@ declare module "@uidotdev/usehooks" { export function useIdle(ms?: number): boolean; - export function useIntersectionObserver( + export function useIntersectionObserver( options?: IntersectionObserverInit - ): [React.MutableRefObject, IntersectionObserverEntry | null]; + ): [React.MutableRefObject, IntersectionObserverEntry | null]; export function useIsClient(): boolean; From 43136313cd5ec125272b1be6a44ff29f15a96019 Mon Sep 17 00:00:00 2001 From: Lynn Fisher Date: Fri, 1 Sep 2023 15:32:00 -0700 Subject: [PATCH 26/80] add reactgg launch banner --- usehooks.com/public/img/bytes-tshirt.png | Bin 0 -> 1524 bytes .../src/components/CountdownBanner.astro | 267 ++++++++++++++++++ usehooks.com/src/pages/index.astro | 2 + usehooks.com/src/styles/globals.css | 4 +- 4 files changed, 271 insertions(+), 2 deletions(-) create mode 100644 usehooks.com/public/img/bytes-tshirt.png create mode 100644 usehooks.com/src/components/CountdownBanner.astro diff --git a/usehooks.com/public/img/bytes-tshirt.png b/usehooks.com/public/img/bytes-tshirt.png new file mode 100644 index 0000000000000000000000000000000000000000..5f59d9c1823c7fbf41620ee654e0a85ee90b807d GIT binary patch literal 1524 zcmb7E`9ISQ9G}+Vo{)scz8jim&bcheXjuFfmKwESKc#gk-=)^~Qa)j=XdgS@}<5n2eu5~Cz9J2jK+=j3qK%+#nD(2vm2 zMM7cCH3d^i944Jsc%7D#7?}_ko*EhuKsoPDB3`t!4aC{l5pd_RIGhF6&cLW1FvJ+4 zs{k|$ruV*FCmGPDsH0*4)Mcnv3tO|FbF!t8`!lO}B>vVw42zq`oX<>XpkAw_#*Q*V zFH`+#p6*xO$3v?@K)>Kb{|VP%5vK!Q0|xMcX%L64u(|k_?|I@FU$kN|x_) zWX`ekAX-~rR)?|z7cBP_^;6PZ#7^SKSopyK?#8%iuiBjK@?fuqEJQOojefXO^i(o@ z+PqF9FoFIcDI`~X55}2=Y(4P1%S-3pvrVmj)NPK}kLNX29FS1m1lt>EYLk6O6HAT{ zSjj0k=BHVzQpcq?vfj`Sxh`Y$cH~EKS&i%?o!nA5Q?D z-x$LovuFI)rN%t1U&OCI^e&uYEjg9>pTNRos}9TdJc@&LoGz|NC}e};Ox+PkKBIqb-daJUugAU# zVhoro5SyDmW6Ju#YZBG57wXnjzZJXk$Nh{Uk|lNpe5Nr_O5D67Gp<%|9`i}C%MacB zQi7WXp_c#2JrMyZ;wZ;le^Fb!M3RXVVBKubUF088EDZ55bjMJw(cDO5o}@4pFWz6fV`w1oo48B4A~0dwE6d6J>neNO(|~ z8LY0gtF-LiaT&&G?Bv=zN|koUn_Fydh; zfr4u*xewB_i(7_EX%3DBa(Bq}!m;fHLb&85QouOGUQ$W@V+Vm^OdTyk8lqN@3Tt+U zN|WE!4vDZ3&zuGB|i{^Z)@{+Shp3-Kc)?koM- z#43ImrW0WgWyc|}2&@;y{@`XaTN=JrimOnl@cP8KU6G=*b#vw78nYbbMmD&T15)KR zZlC`>xvDY-+OIU|>gqqiPw7yEx)tHSu@J1$uGtp^7G~boxt6CqvA+o*I|9+V9`Bp_ EFJVcZH2?qr literal 0 HcmV?d00001 diff --git a/usehooks.com/src/components/CountdownBanner.astro b/usehooks.com/src/components/CountdownBanner.astro new file mode 100644 index 0000000..84de6ea --- /dev/null +++ b/usehooks.com/src/components/CountdownBanner.astro @@ -0,0 +1,267 @@ +--- +import Button from "../components/Button.astro"; +--- + + + + + + + diff --git a/usehooks.com/src/pages/index.astro b/usehooks.com/src/pages/index.astro index d9846b7..c3289ca 100644 --- a/usehooks.com/src/pages/index.astro +++ b/usehooks.com/src/pages/index.astro @@ -1,6 +1,7 @@ --- import { getCollection } from "astro:content"; import Layout from "../layouts/Layout.astro"; +import CountdownBanner from "../components/CountdownBanner.astro"; import NavMain from "../sections/NavMain.astro"; import HomeHero from "../sections/HomeHero.astro"; import HooksList from "../components/search/HooksList"; @@ -13,6 +14,7 @@ const hooks = await getCollection("hooks"); title="useHooks – The React Hooks Library" description="A collection of modern, server-safe React hooks – from the ui.dev team" > + diff --git a/usehooks.com/src/styles/globals.css b/usehooks.com/src/styles/globals.css index 8ee6d8a..05287c4 100644 --- a/usehooks.com/src/styles/globals.css +++ b/usehooks.com/src/styles/globals.css @@ -152,7 +152,7 @@ video { --brand-pink: #f38ba3; --brand-green: #0ba95b; --brand-purple: #7b5ea7; - --brand-biege: #f9f4da; + --brand-beige: #f9f4da; --brand-blue: #12b5e5; --brand-orange: #fc7428; --brand-red: #ed203d; @@ -161,7 +161,7 @@ video { --magesticPurple: #9d7dce; --red: var(--brand-red); - --white: var(--brand-biege); + --white: var(--brand-beige); --purple: var(--brand-purple); --black: var(--brand-coal); --blue: var(--brand-blue); From b9b6980e824374083047583075f6e107d8bab7d9 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Fri, 1 Sep 2023 20:49:30 -0600 Subject: [PATCH 27/80] Remove pageXOffset and pageYOffset in favor or scrollX and scrollY --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 652fa5f..25f420e 100644 --- a/index.js +++ b/index.js @@ -1289,7 +1289,7 @@ export function useWindowScroll() { React.useLayoutEffect(() => { const handleScroll = () => { - setState({ x: window.pageXOffset, y: window.pageYOffset }); + setState({ x: window.scrollX, y: window.scrollY }); }; handleScroll(); From c1a0fa8134591d548048bc202657b74d5d0587b7 Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 3 Sep 2023 16:05:02 +0800 Subject: [PATCH 28/80] Add type declaration for useLocalStorage --- index.d.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/index.d.ts b/index.d.ts index 86f9f50..dc45384 100644 --- a/index.d.ts +++ b/index.d.ts @@ -175,6 +175,11 @@ declare module "@uidotdev/usehooks" { export function useList(defaultList?: T[]): [T[], CustomList]; + export function useLocalStorage( + key: string, + initialValue?: T + ): [T, React.Dispatch>]; + export function useLockBodyScroll(): void; export function useLongPress( From c4ec69af864fd2c5dd0b5f31c36fc5818f690800 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Sun, 3 Sep 2023 13:43:57 -0600 Subject: [PATCH 29/80] Update useToggle --- index.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 25f420e..ac5d6e8 100644 --- a/index.js +++ b/index.js @@ -1230,7 +1230,13 @@ export function useThrottle(value, interval = 500) { } export function useToggle(initialValue) { - const [on, setOn] = React.useState(initialValue); + const [on, setOn] = React.useState(() => { + if (typeof initialValue === "boolean") { + return initialValue; + } + + return Boolean(initialValue); + }); const handleToggle = React.useCallback((value) => { if (typeof value === "boolean") { From cbe428b6345ee2bc38762e2d49c155be7e1a1f88 Mon Sep 17 00:00:00 2001 From: Lynn Fisher Date: Mon, 4 Sep 2023 11:03:18 -0700 Subject: [PATCH 30/80] add reactgg logo --- .../src/components/CountdownBanner.astro | 20 +++++++++++++++---- usehooks.com/src/styles/globals.css | 1 - 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/usehooks.com/src/components/CountdownBanner.astro b/usehooks.com/src/components/CountdownBanner.astro index 84de6ea..1a2b069 100644 --- a/usehooks.com/src/components/CountdownBanner.astro +++ b/usehooks.com/src/components/CountdownBanner.astro @@ -2,8 +2,9 @@ import Button from "../components/Button.astro"; --- - + + --> + --> - - From c8f9bda1835394b689bc2820655f282ed6574b56 Mon Sep 17 00:00:00 2001 From: Jenia Brook Date: Wed, 13 Sep 2023 08:56:18 +0300 Subject: [PATCH 40/80] Export type definition for `useSessionStorage` --- index.d.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/index.d.ts b/index.d.ts index 86f9f50..cfe7f00 100644 --- a/index.d.ts +++ b/index.d.ts @@ -225,6 +225,11 @@ declare module "@uidotdev/usehooks" { } ): "idle" | "loading" | "ready" | "error"; + export function useSessionStorage( + key: string, + initialValue: T + ): [T, React.Dispatch>]; + export function useSet(values?: T[]): Set; export function useSpeech(text: string, options?: SpeechOptions): SpeechState; From e9b4f9d51e144d4abec57c1b7eecce2671302ced Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Mon, 18 Sep 2023 09:46:48 -0600 Subject: [PATCH 41/80] Update useIsClient description --- usehooks.com/src/content/hooks/useIsClient.mdx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/usehooks.com/src/content/hooks/useIsClient.mdx b/usehooks.com/src/content/hooks/useIsClient.mdx index 8492ef3..9efdd84 100644 --- a/usehooks.com/src/content/hooks/useIsClient.mdx +++ b/usehooks.com/src/content/hooks/useIsClient.mdx @@ -13,11 +13,7 @@ import HookDescription from "../../components/HookDescription.astro"; import StaticCodeContainer from "../../components/StaticCodeContainer.astro"; - The useIsClient hook is useful for determining whether the code is running on - the client-side or server-side. The hook initializes a state variable called - "isClient" and sets its initial value to false. This information can be - utilized in conditional rendering, handling browser-specific behavior, or - making API calls that should only be executed on the client-side. + useIsClient is useful for determining if it's safe to run certain client-only hooks like useMediaQuery or useLocalStorage. It returns a boolean determining if React's useEffect hook has finished running (which means the app is being rendered on the client and it's safe to use browser specific APIs).
From f2794aa6799b129c77ddc914d85e5222d8488796 Mon Sep 17 00:00:00 2001 From: Benjamin Adam Date: Mon, 18 Sep 2023 09:03:02 -0700 Subject: [PATCH 42/80] 2.3.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1175c6c..39635ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@uidotdev/usehooks", - "version": "2.2.0", + "version": "2.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@uidotdev/usehooks", - "version": "2.2.0", + "version": "2.3.0", "license": "MIT", "devDependencies": { "@types/react": "^18.2.20", diff --git a/package.json b/package.json index 521d2ab..fdb601b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uidotdev/usehooks", - "version": "2.2.0", + "version": "2.3.0", "description": "A collection of modern, server-safe React hooks – from the ui.dev team", "type": "module", "repository": "uidotdev/usehooks", From 9ba3faf0e6510f9c668428f87b2c90e94c380297 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Tue, 19 Sep 2023 10:49:56 -0600 Subject: [PATCH 43/80] Fixes #236 --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index ac5d6e8..981bd68 100644 --- a/index.js +++ b/index.js @@ -367,7 +367,7 @@ const useHistoryStateReducer = (state, action) => { }; } else if (action.type === "CLEAR") { return { - ...initialState, + ...initialUseHistoryStateState, present: action.initialPresent, }; } else { From 546df269e31a9ba10e0f6902fbda66d5eab7caa7 Mon Sep 17 00:00:00 2001 From: Benjamin Adam Date: Tue, 19 Sep 2023 09:56:56 -0700 Subject: [PATCH 44/80] 2.3.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 39635ae..08dfab0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@uidotdev/usehooks", - "version": "2.3.0", + "version": "2.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@uidotdev/usehooks", - "version": "2.3.0", + "version": "2.3.1", "license": "MIT", "devDependencies": { "@types/react": "^18.2.20", diff --git a/package.json b/package.json index fdb601b..b9d1229 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uidotdev/usehooks", - "version": "2.3.0", + "version": "2.3.1", "description": "A collection of modern, server-safe React hooks – from the ui.dev team", "type": "module", "repository": "uidotdev/usehooks", From cc2c0ed45284a668c33b501fde6ba6efa4310964 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Fri, 6 Oct 2023 08:05:08 -0600 Subject: [PATCH 45/80] Update useCounter implementation --- index.js | 69 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/index.js b/index.js index 981bd68..28f454c 100644 --- a/index.js +++ b/index.js @@ -162,13 +162,13 @@ export function useCopyToClipboard() { export function useCounter(startingValue = 0, options = {}) { const { min, max } = options; - if (min && startingValue < min) { + if (typeof min === "number" && startingValue < min) { throw new Error( `Your starting value of ${startingValue} is less than your min of ${min}.` ); } - if (max && startingValue > max) { + if (typeof max === "number" && startingValue > max) { throw new Error( `Your starting value of ${startingValue} is greater than your max of ${max}.` ); @@ -176,47 +176,50 @@ export function useCounter(startingValue = 0, options = {}) { const [count, setCount] = React.useState(startingValue); - const increment = () => { - const nextCount = count + 1; - if (max && nextCount > max) { - return; - } + const increment = React.useCallback(() => { + setCount((c) => { + const nextCount = c + 1; - setCount(nextCount); - }; + if (typeof max === "number" && nextCount > max) { + return c; + } - const decrement = () => { - const nextCount = count - 1; - if (min && nextCount < min) { - return; - } + return nextCount; + }); + }, [max]); - setCount(nextCount); - }; + const decrement = React.useCallback(() => { + setCount((c) => { + const nextCount = c - 1; - const set = (nextCount) => { - if (max && nextCount > max) { - return; - } + if (typeof min === "number" && nextCount < min) { + return c; + } - if (min && nextCount < min) { - return; - } + return nextCount; + }); + }, [min]); - if (nextCount === count) { - return; - } + const set = React.useCallback( + (nextCount) => { + setCount((c) => { + if (typeof max === "number" && nextCount > max) { + return c; + } - setCount(nextCount); - }; + if (typeof min === "number" && nextCount < min) { + return c; + } - const reset = () => { - if (count === startingValue) { - return; - } + return nextCount; + }); + }, + [max, min] + ); + const reset = React.useCallback(() => { setCount(startingValue); - }; + }, [startingValue]); return [ count, From c90921adec84109c905fb09de0548850c85ba798 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Fri, 6 Oct 2023 08:09:27 -0600 Subject: [PATCH 46/80] Update useLockBodyScroll implementation --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 28f454c..746929d 100644 --- a/index.js +++ b/index.js @@ -634,7 +634,7 @@ export function useLocalStorage(key, initialValue) { } export function useLockBodyScroll() { - React.useEffect(() => { + React.useLayoutEffect(() => { const originalStyle = window.getComputedStyle(document.body).overflow; document.body.style.overflow = "hidden"; return () => { From e27386f20770200fad1fbef683ada2704ec7aea6 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Fri, 6 Oct 2023 08:32:02 -0600 Subject: [PATCH 47/80] Update useFavicon implementation --- index.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 746929d..5a1ffd4 100644 --- a/index.js +++ b/index.js @@ -266,13 +266,17 @@ export function useDocumentTitle(title) { export function useFavicon(url) { React.useEffect(() => { - const link = - document.querySelector("link[rel*='icon']") || - document.createElement("link"); - link.type = "image/x-icon"; - link.rel = "shortcut icon"; - link.href = url; - document.getElementsByTagName("head")[0].appendChild(link); + let link = document.querySelector(`link[rel~="icon"]`); + + if (!link) { + link = document.createElement("link"); + link.type = "image/x-icon"; + link.rel = "icon"; + link.href = url; + document.head.appendChild(link); + } else { + link.href = url; + } }, [url]); } From 32dbf10c57e599f245d0a64cd3e8f1849a4b7401 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Fri, 6 Oct 2023 09:51:56 -0600 Subject: [PATCH 48/80] Update useObjectState implementation --- index.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 5a1ffd4..791655c 100644 --- a/index.js +++ b/index.js @@ -38,9 +38,13 @@ function throttle(cb, ms) { }; } -const dispatchStorageEvent = (key, newValue) => { +function isPlainObject(value) { + return Object.prototype.toString.call(value) === "[object Object]"; +} + +function dispatchStorageEvent(key, newValue) { window.dispatchEvent(new StorageEvent("storage", { key, newValue })); -}; +} export function useBattery() { const [state, setState] = React.useState({ @@ -910,14 +914,16 @@ export function useObjectState(initialValue) { setState((s) => { const newState = arg(s); - return { - ...s, - ...newState, - }; + if (isPlainObject(newState)) { + return { + ...s, + ...newState, + }; + } }); } - if (typeof arg === "object") { + if (isPlainObject(arg)) { setState((s) => ({ ...s, ...arg, From 0d2e15e5c4ad81ed81917fd5f1a99e75e47b8dbc Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Fri, 6 Oct 2023 15:16:04 -0600 Subject: [PATCH 49/80] Update useScript implementation --- index.js | 42 ++++++-------------- usehooks.com/src/content/hooks/useScript.mdx | 2 +- 2 files changed, 14 insertions(+), 30 deletions(-) diff --git a/index.js b/index.js index 791655c..1a7aad4 100644 --- a/index.js +++ b/index.js @@ -1081,52 +1081,36 @@ export function useRenderInfo(name = "Unknown") { } export function useScript(src, options = {}) { - const [status, setStatus] = React.useState(() => { - if (!src) { - return "idle"; - } - - return "loading"; - }); - - const cachedScriptStatuses = React.useRef({}); + const [status, setStatus] = React.useState("loading"); + const optionsRef = React.useRef(options); React.useEffect(() => { - if (!src) return; - - const cachedScriptStatus = cachedScriptStatuses.current[src]; - if (cachedScriptStatus === "ready" || cachedScriptStatus === "error") { - setStatus(cachedScriptStatus); - return; - } - let script = document.querySelector(`script[src="${src}"]`); - if (!script) { + if (script === null) { script = document.createElement("script"); script.src = src; script.async = true; document.body.appendChild(script); } - const handleScriptStatus = (event) => { - const newStatus = event.type === "load" ? "ready" : "error"; - setStatus(newStatus); - cachedScriptStatuses.current[src] = newStatus; - }; + const handleScriptLoad = () => setStatus("ready"); + const handleScriptError = () => setStatus("error"); + + script.addEventListener("load", handleScriptLoad); + script.addEventListener("error", handleScriptError); - script.addEventListener("load", handleScriptStatus); - script.addEventListener("error", handleScriptStatus); + const removeOnUnmount = optionsRef.current.removeOnUnmount; return () => { - script.removeEventListener("load", handleScriptStatus); - script.removeEventListener("error", handleScriptStatus); + script.removeEventListener("load", handleScriptLoad); + script.removeEventListener("error", handleScriptError); - if (options.removeOnUnmount === true) { + if (removeOnUnmount === true) { script.remove(); } }; - }, [src, options.removeOnUnmount]); + }, [src]); return status; } diff --git a/usehooks.com/src/content/hooks/useScript.mdx b/usehooks.com/src/content/hooks/useScript.mdx index d11fa79..234f649 100644 --- a/usehooks.com/src/content/hooks/useScript.mdx +++ b/usehooks.com/src/content/hooks/useScript.mdx @@ -38,7 +38,7 @@ import StaticCodeContainer from "../../components/StaticCodeContainer.astro";
| Name | Type | Description | | ------ | ------ | ----------- | - | status | string | This represents the status of the script load. Possible values are: `idle`, `loading`, `ready`, and `error`. | + | status | string | This represents the status of the script load. Possible values are: `loading`, `ready`, and `error`. |
From 71c6c5801500b24e20142a9e7e453fc666f0f09e Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Mon, 9 Oct 2023 13:10:42 -0600 Subject: [PATCH 50/80] Update useHover implementation --- index.js | 47 ++++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/index.js b/index.js index 1a7aad4..23e5916 100644 --- a/index.js +++ b/index.js @@ -425,31 +425,40 @@ export function useHistoryState(initialPresent = {}) { export function useHover() { const [hovering, setHovering] = React.useState(false); - const ref = React.useRef(null); - - React.useEffect(() => { - const node = ref.current; + const previousNode = React.useRef(null); - if (!node) return; + const handleMouseEnter = React.useCallback(() => { + setHovering(true); + }, []); - const handleMouseEnter = () => { - setHovering(true); - }; + const handleMouseLeave = React.useCallback(() => { + setHovering(false); + }, []); - const handleMouseLeave = () => { - setHovering(false); - }; + const customRef = React.useCallback( + (node) => { + if (previousNode.current instanceof HTMLElement) { + previousNode.current.removeEventListener( + "mouseenter", + handleMouseEnter + ); + previousNode.current.removeEventListener( + "mouseleave", + handleMouseLeave + ); + } - node.addEventListener("mouseenter", handleMouseEnter); - node.addEventListener("mouseleave", handleMouseLeave); + if (node instanceof HTMLElement) { + node.addEventListener("mouseenter", handleMouseEnter); + node.addEventListener("mouseleave", handleMouseLeave); + } - return () => { - node.removeEventListener("mouseenter", handleMouseEnter); - node.removeEventListener("mouseleave", handleMouseLeave); - }; - }, []); + previousNode.current = node; + }, + [handleMouseEnter, handleMouseLeave] + ); - return [ref, hovering]; + return [customRef, hovering]; } export function useIdle(ms = 1000 * 60) { From 5cbb6df7e7a42d5a720437e1267f5de86f7e2ca8 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Mon, 9 Oct 2023 13:11:28 -0600 Subject: [PATCH 51/80] Update useMeasure implementation --- index.js | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/index.js b/index.js index 23e5916..537d6d5 100644 --- a/index.js +++ b/index.js @@ -750,31 +750,35 @@ export function useMap(initialState) { } export function useMeasure() { - const ref = React.useRef(null); - const [rect, setRect] = React.useState({ + const [dimensions, setDimensions] = React.useState({ width: null, height: null, }); - React.useLayoutEffect(() => { - if (!ref.current) return; - - const observer = new ResizeObserver(([entry]) => { - if (entry && entry.contentRect) { - setRect({ - width: entry.contentRect.width, - height: entry.contentRect.height, - }); - } - }); + const previousObserver = React.useRef(null); - observer.observe(ref.current); - return () => { - observer.disconnect(); - }; + const customRef = React.useCallback((node) => { + if (previousObserver.current) { + previousObserver.current.disconnect(); + previousObserver.current = null; + } + + if (node instanceof HTMLElement) { + const observer = new ResizeObserver(([entry]) => { + if (entry && entry.borderBoxSize) { + const { inlineSize: width, blockSize: height } = + entry.borderBoxSize[0]; + + setDimensions({ width, height }); + } + }); + + observer.observe(node); + previousObserver.current = observer; + } }, []); - return [ref, rect]; + return [customRef, dimensions]; } export function useMediaQuery(query) { From 4c05ee0585e5ad57e9baab35063ea21080629065 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Mon, 9 Oct 2023 13:13:21 -0600 Subject: [PATCH 52/80] Update useIntersectionObserver implementation --- index.js | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/index.js b/index.js index 537d6d5..560c703 100644 --- a/index.js +++ b/index.js @@ -510,33 +510,34 @@ export function useIdle(ms = 1000 * 60) { } export function useIntersectionObserver(options = {}) { - const { threshold = 1, root = null, rootMargin = "0%" } = options; - const ref = React.useRef(null); + const { threshold = 1, root = null, rootMargin = "0px" } = options; const [entry, setEntry] = React.useState(null); - React.useEffect(() => { - const node = ref?.current; - - if (!node || typeof IntersectionObserver !== "function") { - return; - } + const previousObserver = React.useRef(null); - const observer = new IntersectionObserver( - ([entry]) => { - setEntry(entry); - }, - { threshold, root, rootMargin } - ); + const customRef = React.useCallback( + (node) => { + if (previousObserver.current) { + previousObserver.current.disconnect(); + previousObserver.current = null; + } - observer.observe(node); + if (node instanceof HTMLElement) { + const observer = new IntersectionObserver( + ([entry]) => { + setEntry(entry); + }, + { threshold, root, rootMargin } + ); - return () => { - setEntry(null); - observer.disconnect(); - }; - }, [threshold, root, rootMargin]); + observer.observe(node); + previousObserver.current = observer; + } + }, + [threshold, root, rootMargin] + ); - return [ref, entry]; + return [customRef, entry]; } export function useIsClient() { From cd819c9d2e218009fcf26693b6c2f5efad52c337 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Mon, 9 Oct 2023 13:15:36 -0600 Subject: [PATCH 53/80] Update useMouse implementation --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 560c703..5c7f84b 100644 --- a/index.js +++ b/index.js @@ -827,8 +827,8 @@ export function useMouse() { if (ref.current instanceof HTMLElement) { const { left, top } = ref.current.getBoundingClientRect(); - const elementPositionX = left + window.pageXOffset; - const elementPositionY = top + window.pageYOffset; + const elementPositionX = left + window.scrollX; + const elementPositionY = top + window.scrollY; const elementX = event.pageX - elementPositionX; const elementY = event.pageY - elementPositionY; From 888e48db79605d6defac64b40c18bd47951dbf77 Mon Sep 17 00:00:00 2001 From: Benjamin Adam Date: Tue, 10 Oct 2023 08:56:52 -0700 Subject: [PATCH 54/80] 2.4.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 08dfab0..4e5bc3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@uidotdev/usehooks", - "version": "2.3.1", + "version": "2.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@uidotdev/usehooks", - "version": "2.3.1", + "version": "2.4.0", "license": "MIT", "devDependencies": { "@types/react": "^18.2.20", diff --git a/package.json b/package.json index b9d1229..f123711 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uidotdev/usehooks", - "version": "2.3.1", + "version": "2.4.0", "description": "A collection of modern, server-safe React hooks – from the ui.dev team", "type": "module", "repository": "uidotdev/usehooks", From e9fcf7ffb9bd648c29c1376abd2bd15550d6a46c Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Tue, 10 Oct 2023 17:28:37 -0600 Subject: [PATCH 55/80] nodeType over instanceof --- index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 5c7f84b..eb382d9 100644 --- a/index.js +++ b/index.js @@ -437,7 +437,7 @@ export function useHover() { const customRef = React.useCallback( (node) => { - if (previousNode.current instanceof HTMLElement) { + if (previousNode.current?.nodeType === Node.ELEMENT_NODE) { previousNode.current.removeEventListener( "mouseenter", handleMouseEnter @@ -448,7 +448,7 @@ export function useHover() { ); } - if (node instanceof HTMLElement) { + if (node?.nodeType === Node.ELEMENT_NODE) { node.addEventListener("mouseenter", handleMouseEnter); node.addEventListener("mouseleave", handleMouseLeave); } @@ -522,7 +522,7 @@ export function useIntersectionObserver(options = {}) { previousObserver.current = null; } - if (node instanceof HTMLElement) { + if (node?.nodeType === Node.ELEMENT_NODE) { const observer = new IntersectionObserver( ([entry]) => { setEntry(entry); @@ -764,7 +764,7 @@ export function useMeasure() { previousObserver.current = null; } - if (node instanceof HTMLElement) { + if (node?.nodeType === Node.ELEMENT_NODE) { const observer = new ResizeObserver(([entry]) => { if (entry && entry.borderBoxSize) { const { inlineSize: width, blockSize: height } = @@ -825,7 +825,7 @@ export function useMouse() { y: event.pageY, }; - if (ref.current instanceof HTMLElement) { + if (ref.current?.nodeType === Node.ELEMENT_NODE) { const { left, top } = ref.current.getBoundingClientRect(); const elementPositionX = left + window.scrollX; const elementPositionY = top + window.scrollY; From 630d43dc787ca475c9ae18c3b988131de6b7de81 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Wed, 11 Oct 2023 12:17:51 -0600 Subject: [PATCH 56/80] Update types for useMeasure, useIntersectionObserver, and useHover --- index.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.d.ts b/index.d.ts index dfcb495..9d8cad2 100644 --- a/index.d.ts +++ b/index.d.ts @@ -160,7 +160,7 @@ declare module "@uidotdev/usehooks" { export function useHistoryState(initialPresent?: T): HistoryState; export function useHover(): [ - React.MutableRefObject, + React.RefCallback, boolean ]; @@ -168,7 +168,7 @@ declare module "@uidotdev/usehooks" { export function useIntersectionObserver( options?: IntersectionObserverInit - ): [React.MutableRefObject, IntersectionObserverEntry | null]; + ): [React.RefCallback, IntersectionObserverEntry | null]; export function useIsClient(): boolean; @@ -191,7 +191,7 @@ declare module "@uidotdev/usehooks" { export function useMap(initialState?: T): Map; export function useMeasure(): [ - React.MutableRefObject, + React.RefCallback, { width: number | null; height: number | null; From 50d08d84d804a2d604ddd9d53a5cc8c7529368a2 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Mon, 16 Oct 2023 12:12:33 -0600 Subject: [PATCH 57/80] Add pixel --- usehooks.com/src/layouts/Layout.astro | 38 +++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/usehooks.com/src/layouts/Layout.astro b/usehooks.com/src/layouts/Layout.astro index eab3712..ba8d9f6 100644 --- a/usehooks.com/src/layouts/Layout.astro +++ b/usehooks.com/src/layouts/Layout.astro @@ -13,12 +13,12 @@ const { ogImage = new URL("/meta/og.jpg", Astro.url), } = Astro.props; -const pathname = Astro.url.pathname - -const url = pathname[pathname.length - 1] === "/" - ? new URL(pathname.slice(0, pathname.length -1), Astro.site) - : new URL(Astro.url.pathname, Astro.site) +const pathname = Astro.url.pathname; +const url = + pathname[pathname.length - 1] === "/" + ? new URL(pathname.slice(0, pathname.length - 1), Astro.site) + : new URL(Astro.url.pathname, Astro.site); --- @@ -90,6 +90,34 @@ const url = pathname[pathname.length - 1] === "/" data-api="/stats/api/event" data-domain="usehooks.com" > + + From bc6df206062dbd461ac5a09885fe60ee447c87ee Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Wed, 18 Oct 2023 09:09:50 -0600 Subject: [PATCH 58/80] Add query.gg to footer --- usehooks.com/src/sections/Footer.astro | 91 +++++++++++++------------- 1 file changed, 46 insertions(+), 45 deletions(-) diff --git a/usehooks.com/src/sections/Footer.astro b/usehooks.com/src/sections/Footer.astro index c6ce3dc..246fc1c 100644 --- a/usehooks.com/src/sections/Footer.astro +++ b/usehooks.com/src/sections/Footer.astro @@ -2,57 +2,58 @@ --- From 0a4eb076f0938a14496ad04ce93a19c66ed42e9a Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Wed, 18 Oct 2023 09:10:58 -0600 Subject: [PATCH 59/80] Add RN to footer --- usehooks.com/src/sections/Footer.astro | 1 + 1 file changed, 1 insertion(+) diff --git a/usehooks.com/src/sections/Footer.astro b/usehooks.com/src/sections/Footer.astro index 246fc1c..86d53ff 100644 --- a/usehooks.com/src/sections/Footer.astro +++ b/usehooks.com/src/sections/Footer.astro @@ -9,6 +9,7 @@ From d057a9ba0f753111f691721d1e31a0bc3e8da440 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Mon, 23 Oct 2023 08:47:30 -0600 Subject: [PATCH 60/80] Re-implement useScript --- index.d.ts | 2 +- index.js | 47 ++++++++++++++------ usehooks.com/src/content/hooks/useScript.mdx | 5 +-- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/index.d.ts b/index.d.ts index 9d8cad2..db01dc2 100644 --- a/index.d.ts +++ b/index.d.ts @@ -229,7 +229,7 @@ declare module "@uidotdev/usehooks" { options?: { removeOnUnmount?: boolean; } - ): "idle" | "loading" | "ready" | "error"; + ): "unknown" | "loading" | "ready" | "error"; export function useSessionStorage( key: string, diff --git a/index.js b/index.js index eb382d9..f6e4fe2 100644 --- a/index.js +++ b/index.js @@ -1101,29 +1101,50 @@ export function useScript(src, options = {}) { React.useEffect(() => { let script = document.querySelector(`script[src="${src}"]`); + const domStatus = script?.getAttribute("data-status"); + if (domStatus) { + setStatus(domStatus); + return; + } + if (script === null) { script = document.createElement("script"); script.src = src; script.async = true; + script.setAttribute("data-status", "loading"); document.body.appendChild(script); - } - const handleScriptLoad = () => setStatus("ready"); - const handleScriptError = () => setStatus("error"); + const handleScriptLoad = () => { + script.setAttribute("data-status", "ready"); + setStatus("ready"); + removeEventListeners(); + }; - script.addEventListener("load", handleScriptLoad); - script.addEventListener("error", handleScriptError); + const handleScriptError = () => { + script.setAttribute("data-status", "error"); + setStatus("error"); + removeEventListeners(); + }; - const removeOnUnmount = optionsRef.current.removeOnUnmount; + const removeEventListeners = () => { + script.removeEventListener("load", handleScriptLoad); + script.removeEventListener("error", handleScriptError); + }; - return () => { - script.removeEventListener("load", handleScriptLoad); - script.removeEventListener("error", handleScriptError); + script.addEventListener("load", handleScriptLoad); + script.addEventListener("error", handleScriptError); - if (removeOnUnmount === true) { - script.remove(); - } - }; + const removeOnUnmount = optionsRef.current.removeOnUnmount; + + return () => { + if (removeOnUnmount === true) { + script.remove(); + removeEventListeners(); + } + }; + } else { + setStatus("unknown"); + } }, [src]); return status; diff --git a/usehooks.com/src/content/hooks/useScript.mdx b/usehooks.com/src/content/hooks/useScript.mdx index 234f649..7a6d81a 100644 --- a/usehooks.com/src/content/hooks/useScript.mdx +++ b/usehooks.com/src/content/hooks/useScript.mdx @@ -17,8 +17,7 @@ import StaticCodeContainer from "../../components/StaticCodeContainer.astro"; scripts into a React component. It manages the loading and status of the script, allowing you to conditionally render components or perform actions based on whether the script has been successfully loaded or encountered an - error. The hook keeps track of the script’s status, such as "loading," - "ready," or "error," and provides this status as a return value. Additionally, + error. The hook keeps track of the script’s status and provides this status as a return value. Additionally, it offers options to remove the script when the component is unmounted, ensuring proper cleanup. @@ -38,7 +37,7 @@ import StaticCodeContainer from "../../components/StaticCodeContainer.astro";
| Name | Type | Description | | ------ | ------ | ----------- | - | status | string | This represents the status of the script load. Possible values are: `loading`, `ready`, and `error`. | + | status | string | This represents the status of the script load, `loading`, `ready`, `error`, or `unknown`. An `unknown` script is one that previously exists in the document, but was not added via `useScript`. |
From fa7f8bf3983cad6358c27e1a0547799b0865a168 Mon Sep 17 00:00:00 2001 From: Benjamin Adam Date: Mon, 23 Oct 2023 11:28:29 -0700 Subject: [PATCH 61/80] 2.4.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4e5bc3a..d920bd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@uidotdev/usehooks", - "version": "2.4.0", + "version": "2.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@uidotdev/usehooks", - "version": "2.4.0", + "version": "2.4.1", "license": "MIT", "devDependencies": { "@types/react": "^18.2.20", diff --git a/package.json b/package.json index f123711..12908aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uidotdev/usehooks", - "version": "2.4.0", + "version": "2.4.1", "description": "A collection of modern, server-safe React hooks – from the ui.dev team", "type": "module", "repository": "uidotdev/usehooks", From 46b16a1dd066081e523a3aa118053541b8721ab2 Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Wed, 1 Nov 2023 14:40:16 -0600 Subject: [PATCH 62/80] add s to reactgg links --- .../src/components/HookDescription.astro | 8 +++-- .../src/components/search/Callout.tsx | 30 +++++++++++-------- usehooks.com/src/sections/Footer.astro | 2 +- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/usehooks.com/src/components/HookDescription.astro b/usehooks.com/src/components/HookDescription.astro index 0206b02..3ca4182 100644 --- a/usehooks.com/src/components/HookDescription.astro +++ b/usehooks.com/src/components/HookDescription.astro @@ -16,11 +16,15 @@ const { name } = Astro.props; class="logo" alt="React.gg" /> -

Want to learn how to build {name} yourself? Check out react.gg – the interactive way to master modern React.

+

+ Want to learn how to build {name} yourself? Check out react.gg – the interactive way to master modern React. +