Skip to content

Commit 729b9dd

Browse files
committed
Merge branch 'main' into experimental
2 parents 86f6030 + 8f3af9b commit 729b9dd

File tree

5 files changed

+106
-184
lines changed

5 files changed

+106
-184
lines changed

index.d.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,7 @@ declare module "@uidotdev/usehooks" {
123123
): React.MutableRefObject<T>;
124124

125125
export function useCopyToClipboard(): [
126-
{
127-
error: Error | null;
128-
text: string | null;
129-
},
126+
string | null,
130127
(value: string) => Promise<void>
131128
];
132129

index.js

Lines changed: 100 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -133,35 +133,34 @@ export function useClickAway(cb) {
133133
return ref;
134134
}
135135

136-
export function useCopyToClipboard() {
137-
const [state, setState] = React.useState({
138-
error: null,
139-
text: null,
140-
});
141-
142-
const copyToClipboard = React.useCallback(async (value) => {
143-
if (!navigator?.clipboard) {
144-
return setState({
145-
error: new Error("Clipboard not supported"),
146-
text: null,
147-
});
148-
}
136+
function oldSchoolCopy(text) {
137+
const tempTextArea = document.createElement("textarea");
138+
tempTextArea.value = text;
139+
document.body.appendChild(tempTextArea);
140+
tempTextArea.select();
141+
document.execCommand("copy");
142+
document.body.removeChild(tempTextArea);
143+
}
149144

150-
const handleSuccess = () => {
151-
setState({
152-
error: null,
153-
text: value,
154-
});
155-
};
145+
export function useCopyToClipboard() {
146+
const [state, setState] = React.useState(null);
156147

157-
const handleFailure = (e) => {
158-
setState({
159-
error: e,
160-
text: null,
161-
});
148+
const copyToClipboard = React.useCallback((value) => {
149+
const handleCopy = async () => {
150+
try {
151+
if (navigator?.clipboard?.writeText) {
152+
await navigator.clipboard.writeText(value);
153+
setState(value);
154+
} else {
155+
throw new Error("writeText not supported");
156+
}
157+
} catch (e) {
158+
oldSchoolCopy(value);
159+
setState(value);
160+
}
162161
};
163162

164-
navigator.clipboard.writeText(value).then(handleSuccess, handleFailure);
163+
handleCopy();
165164
}, []);
166165

167166
return [state, copyToClipboard];
@@ -493,38 +492,38 @@ const initialUseHistoryStateState = {
493492

494493
const useHistoryStateReducer = (state, action) => {
495494
const { past, present, future } = state;
496-
switch (action.type) {
497-
case "UNDO":
498-
return {
499-
past: past.slice(0, past.length - 1),
500-
present: past[past.length - 1],
501-
future: [present, ...future],
502-
};
503-
case "REDO":
504-
return {
505-
past: [...past, present],
506-
present: future[0],
507-
future: future.slice(1),
508-
};
509-
case "SET":
510-
const { newPresent } = action;
511495

512-
if (action.newPresent === present) {
513-
return state;
514-
}
496+
if (action.type === "UNDO") {
497+
return {
498+
past: past.slice(0, past.length - 1),
499+
present: past[past.length - 1],
500+
future: [present, ...future],
501+
};
502+
} else if (action.type === "REDO") {
503+
return {
504+
past: [...past, present],
505+
present: future[0],
506+
future: future.slice(1),
507+
};
508+
} else if (action.type === "SET") {
509+
const { newPresent } = action;
515510

516-
return {
517-
past: [...past, present],
518-
present: newPresent,
519-
future: [],
520-
};
521-
case "CLEAR":
522-
return {
523-
...initialUseHistoryStateState,
524-
present: action.initialPresent,
525-
};
526-
default:
527-
throw new Error("Unsupported action type");
511+
if (action.newPresent === present) {
512+
return state;
513+
}
514+
515+
return {
516+
past: [...past, present],
517+
present: newPresent,
518+
future: [],
519+
};
520+
} else if (action.type === "CLEAR") {
521+
return {
522+
...initialState,
523+
present: action.initialPresent,
524+
};
525+
} else {
526+
throw new Error("Unsupported action type");
528527
}
529528
};
530529

@@ -764,40 +763,29 @@ export function useKeyPress(key, cb, options = {}) {
764763
export function useList(defaultList = []) {
765764
const [list, setList] = React.useState(defaultList);
766765

767-
const methods = React.useMemo(() => {
768-
const set = (l) => {
769-
setList(l);
770-
};
771-
772-
const push = (element) => {
773-
setList((l) => [...l, element]);
774-
};
775-
776-
const removeAt = (index) => {
777-
setList((l) => [...l.slice(0, index), ...l.slice(index + 1)]);
778-
};
766+
const set = React.useCallback((l) => {
767+
setList(l);
768+
}, []);
779769

780-
const insertAt = (index, element) => {
781-
setList((l) => [...l.slice(0, index), element, ...l.slice(index)]);
782-
};
770+
const push = React.useCallback((element) => {
771+
setList((l) => [...l, element]);
772+
}, []);
783773

784-
const updateAt = (index, element) => {
785-
setList((l) => l.map((e, i) => (i === index ? element : e)));
786-
};
774+
const removeAt = React.useCallback((index) => {
775+
setList((l) => [...l.slice(0, index), ...l.slice(index + 1)]);
776+
}, []);
787777

788-
const clear = () => setList([]);
778+
const insertAt = React.useCallback((index, element) => {
779+
setList((l) => [...l.slice(0, index), element, ...l.slice(index)]);
780+
}, []);
789781

790-
return {
791-
set,
792-
push,
793-
removeAt,
794-
insertAt,
795-
updateAt,
796-
clear,
797-
};
782+
const updateAt = React.useCallback((index, element) => {
783+
setList((l) => l.map((e, i) => (i === index ? element : e)));
798784
}, []);
799785

800-
return [list, methods];
786+
const clear = React.useCallback(() => setList([]), []);
787+
788+
return [list, { set, push, removeAt, insertAt, updateAt, clear }];
801789
}
802790

803791
export function useLocalStorage(key, initialValue) {
@@ -1064,8 +1052,6 @@ export function useMouse() {
10641052
const elementX = event.pageX - elementPositionX;
10651053
const elementY = event.pageY - elementPositionY;
10661054

1067-
newState.elementX = elementX;
1068-
newState.elementY = elementY;
10691055
newState.elementX = elementX;
10701056
newState.elementY = elementY;
10711057
newState.elementPositionX = elementPositionX;
@@ -1090,18 +1076,23 @@ export function useMouse() {
10901076
return [state, ref];
10911077
}
10921078

1093-
export function useNetworkState() {
1094-
const connection =
1079+
const getConnection = () => {
1080+
return (
10951081
navigator?.connection ||
10961082
navigator?.mozConnection ||
1097-
navigator?.webkitConnection;
1083+
navigator?.webkitConnection
1084+
);
1085+
};
10981086

1087+
export function useNetworkState() {
10991088
const cache = React.useRef({});
11001089

11011090
const subscribe = React.useCallback((callback) => {
11021091
window.addEventListener("online", callback, { passive: true });
11031092
window.addEventListener("offline", callback, { passive: true });
11041093

1094+
const connection = getConnection();
1095+
11051096
if (connection) {
11061097
connection.addEventListener("change", callback, { passive: true });
11071098
}
@@ -1118,6 +1109,7 @@ export function useNetworkState() {
11181109

11191110
const getSnapshot = () => {
11201111
const online = navigator.onLine;
1112+
const connection = getConnection();
11211113

11221114
const nextState = {
11231115
online,
@@ -1491,57 +1483,6 @@ export function useSet(values) {
14911483
return setRef.current;
14921484
}
14931485

1494-
export function useSpeech(text, options) {
1495-
const [state, setState] = React.useState(() => {
1496-
const { lang = "default", name = "" } = options.voice || {};
1497-
return {
1498-
isPlaying: false,
1499-
status: "init",
1500-
lang: options.lang || "default",
1501-
voiceInfo: { lang, name },
1502-
rate: options.rate || 1,
1503-
pitch: options.pitch || 1,
1504-
volume: options.volume || 1,
1505-
};
1506-
});
1507-
1508-
const optionsRef = React.useRef(options);
1509-
1510-
React.useEffect(() => {
1511-
const handlePlay = () => {
1512-
setState((s) => {
1513-
return { ...s, isPlaying: true, status: "play" };
1514-
});
1515-
};
1516-
1517-
const handlePause = () => {
1518-
setState((s) => {
1519-
return { ...s, isPlaying: false, status: "pause" };
1520-
});
1521-
};
1522-
1523-
const handleEnd = () => {
1524-
setState((s) => {
1525-
return { ...s, isPlaying: false, status: "end" };
1526-
});
1527-
};
1528-
1529-
const utterance = new SpeechSynthesisUtterance(text);
1530-
optionsRef.current.lang && (utterance.lang = optionsRef.current.lang);
1531-
optionsRef.current.voice && (utterance.voice = optionsRef.current.voice);
1532-
utterance.rate = optionsRef.current.rate || 1;
1533-
utterance.pitch = optionsRef.current.pitch || 1;
1534-
utterance.volume = optionsRef.current.volume || 1;
1535-
utterance.onstart = handlePlay;
1536-
utterance.onpause = handlePause;
1537-
utterance.onresume = handlePlay;
1538-
utterance.onend = handleEnd;
1539-
window.speechSynthesis.speak(utterance);
1540-
}, [text]);
1541-
1542-
return state;
1543-
}
1544-
15451486
export function useThrottle(value, interval = 500) {
15461487
const [throttledValue, setThrottledValue] = React.useState(value);
15471488
const lastUpdated = React.useRef();
@@ -1596,27 +1537,30 @@ export function useToggle(initialValue) {
15961537
return [on, handleToggle];
15971538
}
15981539

1599-
export function useVisibilityChange() {
1600-
const [documentVisible, setDocumentVisibility] = React.useState(true);
1540+
const useVisibilityChangeSubscribe = (callback) => {
1541+
document.addEventListener("visibilitychange", callback);
16011542

1602-
React.useEffect(() => {
1603-
const handleChange = () => {
1604-
if (document.visibilityState !== "visible") {
1605-
setDocumentVisibility(false);
1606-
} else {
1607-
setDocumentVisibility(true);
1608-
}
1609-
};
1610-
handleChange();
1543+
return () => {
1544+
document.removeEventListener("visibilitychange", callback);
1545+
};
1546+
};
16111547

1612-
document.addEventListener("visibilitychange", handleChange);
1548+
const getVisibilityChangeSnapshot = () => {
1549+
return document.visibilityState;
1550+
};
16131551

1614-
return () => {
1615-
document.removeEventListener("visibilitychange", handleChange);
1616-
};
1617-
}, []);
1552+
const getVisibilityChangeServerSnapshot = () => {
1553+
throw Error("useVisibilityChange is a client-only hook");
1554+
};
1555+
1556+
export function useVisibilityChange() {
1557+
const visibilityState = React.useSyncExternalStore(
1558+
useVisibilityChangeSubscribe,
1559+
getVisibilityChangeSnapshot,
1560+
getVisibilityChangeServerSnapshot
1561+
);
16181562

1619-
return documentVisible;
1563+
return visibilityState === "visible";
16201564
}
16211565

16221566
export function useWindowScroll() {

usehooks.com/src/content/hooks/useCopyToClipboard.mdx

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,6 @@ import StaticCodeContainer from "../../components/StaticCodeContainer.astro";
2323
</HookDescription>
2424

2525
<div class="reference">
26-
### Parameters
27-
28-
<div class="table-container">
29-
| Name | Type | Description |
30-
| ----- | ------ | ---------------------------------------- |
31-
| value | string | The value to be copied to the clipboard. |
32-
</div>
33-
3426
### Return Value
3527

3628
The `useCopyToClipboard` hook returns an array with the following elements:

usehooks.com/src/content/hooks/useDefault.mdx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,7 @@ import HookDescription from "../../components/HookDescription.astro";
1313
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";
1414

1515
<HookDescription name={frontmatter.name}>
16-
The useDefault hook is useful for managing state in functional components with
17-
default values. The hook then checks if the "state" is undefined or null. If
18-
it is, it returns an array containing the "defaultValue" and the "setState"
19-
function, allowing the component to set a default value and update the state
20-
accordingly. On the other hand, if the "state" is defined, it returns an array
21-
with the current "state" value and the "setState" function, enabling normal
22-
state management.
16+
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.
2317
</HookDescription>
2418

2519
<div class="reference">
@@ -28,7 +22,7 @@ import StaticCodeContainer from "../../components/StaticCodeContainer.astro";
2822
<div class="table-container">
2923
| Name | Type | Description |
3024
| ------------ | -------- | ----------- |
31-
| initialValue | any | This is the initial state value provided when calling `useDefault`. |
25+
| initialValue | any | The initial value of the state returned from `useDefault` |
3226
| defaultValue | any | The default value to be used if the state is `undefined` or `null`. |
3327
</div>
3428

0 commit comments

Comments
 (0)