From afabace91238bf5546f3ddeba02d8df345ad5f7e Mon Sep 17 00:00:00 2001 From: Daniel Castillo Date: Wed, 25 Sep 2024 10:16:46 -0400 Subject: [PATCH] feat: add hooks for ts and tsx #2 --- package.json | 8 + snippets/ts.code-snippets | 1800 +++++++++++++++++++++++++++++++++++++ 2 files changed, 1808 insertions(+) create mode 100644 snippets/ts.code-snippets diff --git a/package.json b/package.json index d96b5a6..43c3e10 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,14 @@ { "language": "javascriptreact", "path": "./snippets/js.code-snippets" + }, + { + "language": "typescript", + "path": "./snippets/ts.code-snippets" + }, + { + "language": "typescriptreact", + "path": "./snippets/ts.code-snippets" } ] } diff --git a/snippets/ts.code-snippets b/snippets/ts.code-snippets new file mode 100644 index 0000000..bf80976 --- /dev/null +++ b/snippets/ts.code-snippets @@ -0,0 +1,1800 @@ +{ + "useArray": { + "prefix": "useArray", + "body": [ + "import { useState } from 'react';", + "", + "export default function useArray(initialArray: T[]) {", + " const [array, setArray] = useState(initialArray);", + "", + " const push = (element: T) => {", + " setArray((prev) => [...prev, element]);", + " };", + "", + " const filter = (callback: (element: T) => boolean) => {", + " setArray((prev) => prev.filter(callback));", + " };", + "", + " const update = (index: number, newElement: T) => {", + " setArray((prev) => [", + " ...prev.slice(0, index),", + " newElement,", + " ...prev.slice(index + 1, prev.length),", + " ]);", + " };", + "", + " const remove = (index: number) => {", + " setArray((prev) => [", + " ...prev.slice(0, index),", + " ...prev.slice(index + 1, prev.length),", + " ]);", + " };", + "", + " const clear = () => {", + " setArray([]);", + " };", + "", + " return {", + " array,", + " set: setArray,", + " push,", + " filter,", + " update,", + " remove,", + " clear,", + " };", + "}" + ], + "description": "React hook to manage and manipulate arrays" + }, + "useAudio": { + "prefix": "useAudio", + "body": [ + "import { useEffect, useState, RefObject } from 'react';", + "", + "export const useAudio = (ref: RefObject) => {", + " const audio = ref.current;", + "", + " const [audioState, setAudioState] = useState({", + " isPaused: audio ? audio?.paused : true,", + " isMuted: audio ? audio?.muted : false,", + " currentVolume: audio ? audio?.volume : 100,", + " currentTime: audio ? audio?.currentTime : 0,", + " });", + "", + " const play = () => {", + " audio?.play();", + " setAudioState((prev) => {", + " return {", + " ...prev,", + " isPaused: false,", + " isMuted: audio ? audio.muted : prev.isMuted,", + " };", + " });", + " };", + "", + " const pause = () => {", + " audio?.pause();", + " setAudioState((prev) => {", + " return {", + " ...prev,", + " isPaused: true,", + " };", + " });", + " };", + "", + " const handlePlayPauseControl = (e: Event) => {", + " setAudioState((prev) => {", + " return {", + " ...prev,", + " isPaused: (e.target as HTMLAudioElement).paused,", + " };", + " });", + " };", + "", + " const togglePause = () => (audio?.paused ? play() : pause());", + "", + " const handleVolume = (delta: number) => {", + " const deltaDecimal = delta / 100;", + "", + " if (audio) {", + " let newVolume = audio?.volume + deltaDecimal;", + "", + " if (newVolume >= 1) {", + " newVolume = 1;", + " } else if (newVolume <= 0) {", + " newVolume = 0;", + " }", + "", + " audio.volume = newVolume;", + " setAudioState((prev) => {", + " return {", + " ...prev,", + " currentVolume: newVolume * 100,", + " };", + " });", + " }", + " };", + "", + " const handleVolumeControl = (e: Event) => {", + " if (e.target && audio) {", + " const newVolume = (e.target as HTMLAudioElement).volume * 100;", + "", + " handleMute(audio.muted);", + " setAudioState((prev) => ({", + " ...prev,", + " currentVolume: newVolume,", + " }));", + " }", + " };", + "", + " const handleMute = (mute: boolean) => {", + " if (audio) {", + " audio.muted = mute;", + " setAudioState((prev) => {", + " return {", + " ...prev,", + " isMuted: mute,", + " };", + " });", + " }", + " };", + "", + " const handleTime = (delta: number = 5) => {", + " if (audio) {", + " let newTime = audio.currentTime + delta;", + "", + " if (newTime >= audio.duration) {", + " newTime = audio.duration;", + " } else if (newTime <= 0) {", + " newTime = 0;", + " }", + "", + " audio.currentTime = newTime;", + " setAudioState((prev) => {", + " return {", + " ...prev,", + " currentTime: newTime,", + " };", + " });", + " }", + " };", + "", + " const handleTimeControl = (e: Event) => {", + " setAudioState((prev) => {", + " return {", + " ...prev,", + " currentTime: (e.target as HTMLAudioElement).currentTime,", + " };", + " });", + " };", + "", + " useEffect(() => {", + " return () => {", + " pause();", + " };", + " }, []);", + "", + " useEffect(() => {", + " if (audio) {", + " audio.addEventListener('volumechange', handleVolumeControl);", + " audio.addEventListener('play', handlePlayPauseControl);", + " audio.addEventListener('pause', handlePlayPauseControl);", + " audio.addEventListener('timeupdate', handleTimeControl);", + "", + " return () => {", + " audio.removeEventListener('volumechange', handleVolumeControl);", + " audio.removeEventListener('play', handlePlayPauseControl);", + " audio.removeEventListener('pause', handlePlayPauseControl);", + " audio.removeEventListener('timeupdate', handleTimeControl);", + " };", + " }", + " }, [audio]);", + "", + " return {", + " ...audioState,", + " play,", + " pause,", + " togglePause,", + " increaseVolume: (increase: number = 5) => handleVolume(increase),", + " decreaseVolume: (decrease: number = 5) => handleVolume(decrease * -1),", + " mute: () => handleMute(true),", + " unmute: () => handleMute(false),", + " toggleMute: () => handleMute(!audio?.muted),", + " forward: (increase: number = 5) => handleTime(increase),", + " back: (decrease: number = 5) => handleTime(decrease * -1),", + " };", + "};" + ], + "description": "React hook to manage an audio" + }, + "useBattery": { + "prefix": "useBattery", + "body": [ + "import { useState, useEffect } from 'react';", + "", + "interface BatteryManager {", + " level: number;", + " charging: boolean;", + " chargingTime: number;", + " dischargingTime: number;", + " addEventListener(", + " type: string,", + " listener: EventListener | EventListenerObject | null,", + " options?: boolean | AddEventListenerOptions", + " ): void;", + " removeEventListener(", + " type: string,", + " listener: EventListener | EventListenerObject | null,", + " options?: boolean | EventListenerOptions", + " ): void;", + "}", + "", + "interface BatteryState {", + " supported: boolean;", + " loading: boolean;", + " level: number | null;", + " charging: boolean | null;", + " chargingTime: number | null;", + " dischargingTime: number | null;", + "}", + "", + "interface NavigatorWithBattery extends Navigator {", + " getBattery: () => Promise;", + "}", + "", + "export const useBattery = () => {", + " const [batteryState, setBatteryState] = useState({", + " supported: true,", + " loading: true,", + " level: null,", + " charging: null,", + " chargingTime: null,", + " dischargingTime: null,", + " });", + "", + " useEffect(() => {", + " const _navigator = navigator as NavigatorWithBattery;", + " let battery: BatteryManager;", + "", + " const handleBatteryChange = () => {", + " setBatteryState({", + " supported: true,", + " loading: false,", + " level: battery.level,", + " charging: battery.charging,", + " chargingTime: battery.chargingTime,", + " dischargingTime: battery.dischargingTime,", + " });", + " };", + "", + " if (!_navigator.getBattery) {", + " setBatteryState((batteryState) => ({", + " ...batteryState,", + " supported: false,", + " loading: false,", + " }));", + " return;", + " }", + "", + " _navigator.getBattery().then((_battery) => {", + " battery = _battery;", + " handleBatteryChange();", + "", + " _battery.addEventListener('levelchange', handleBatteryChange);", + " _battery.addEventListener('chargingchange', handleBatteryChange);", + " _battery.addEventListener('chargingtimechange', handleBatteryChange);", + " _battery.addEventListener('dischargingtimechange', handleBatteryChange);", + " });", + "", + " return () => {", + " if (battery) {", + " battery.removeEventListener('levelchange', handleBatteryChange);", + " battery.removeEventListener('chargingchange', handleBatteryChange);", + " battery.removeEventListener('chargingtimechange', handleBatteryChange);", + " battery.removeEventListener(", + " 'dischargingtimechange',", + " handleBatteryChange", + " );", + " }", + " };", + " }, []);", + "", + " return batteryState;", + "};" + ], + "description": "React hook to track the battery status of a user's device" + }, + "useClipboard": { + "prefix": "useClipboard", + "body": [ + "import { useState } from 'react';", + "", + "export const useClipboard = () => {", + " const [copiedText, setCopiedText] = useState('');", + "", + " const copyToClipboard = (value: string) => {", + " return new Promise((resolve, reject) => {", + " try {", + " if (navigator?.clipboard?.writeText) {", + " navigator.clipboard", + " .writeText(value)", + " .then(() => {", + " setCopiedText(value);", + " resolve(value);", + " })", + " .catch((e) => {", + " setCopiedText(null);", + " reject(e);", + " });", + " } else {", + " setCopiedText(null);", + " throw new Error('Clipboard not supported');", + " }", + " } catch (e) {", + " reject(e);", + " }", + " });", + " };", + "", + " return { copiedText, copyToClipboard };", + "};" + ], + "description": "React hook to copy to the clipboard" + }, + "useCountdown": { + "prefix": "useCountdown", + "body": [ + "import { useState, useEffect } from 'react';", + "", + "interface Counter {", + " current: string;", + " isPaused: boolean;", + " isOver: boolean;", + " pause: () => void;", + " play: () => void;", + " reset: () => void;", + " togglePause: () => void;", + "}", + "", + "export const useCountdown = (min: number, max: number): Counter => {", + " const [count, setCount] = useState(max);", + " const [paused, setPaused] = useState(false);", + " const [isOver, setIsOver] = useState(false);", + "", + " useEffect(() => {", + " if (paused) {", + " return;", + " }", + "", + " const interval = setInterval(() => {", + " setCount((prev) => prev - 1);", + " }, 1000);", + "", + " if (count <= min) {", + " setIsOver(true);", + " clearInterval(interval);", + " return;", + " }", + "", + " return () => clearInterval(interval);", + " }, [count, min, max, paused]);", + "", + " return {", + " current: count.toString(),", + " isPaused: paused,", + " isOver,", + " pause: () => setPaused(true),", + " play: () => setPaused(false),", + " reset: () => {", + " setIsOver(false);", + " setCount(max);", + " },", + " togglePause: () => {", + " setPaused(!paused);", + " },", + " };", + "};" + ], + "description": "React hook to create a countdown functionality" + }, + "useCountup": { + "prefix": "useCountup", + "body": [ + "import { useState, useEffect } from 'react';", + "", + "interface Counter {", + " current: string;", + " isPaused: boolean;", + " isOver: boolean;", + " pause: () => void;", + " play: () => void;", + " reset: () => void;", + " togglePause: () => void;", + "}", + "", + "export const useCountup = (min: number, max: number): Counter => {", + " const [count, setCount] = useState(min);", + " const [paused, setPaused] = useState(false);", + " const [isOver, setIsOver] = useState(false);", + "", + " useEffect(() => {", + " if (paused) {", + " return;", + " }", + "", + " const interval = setInterval(() => {", + " setCount((prev) => prev + 1);", + " }, 1000);", + "", + " if (count >= max) {", + " setIsOver(true);", + " clearInterval(interval);", + " return;", + " }", + "", + " return () => clearInterval(interval);", + " }, [count, min, max, paused]);", + "", + " return {", + " current: count.toString(),", + " isPaused: paused,", + " isOver,", + " pause: () => setPaused(true),", + " play: () => setPaused(false),", + " reset: () => {", + " setIsOver(false);", + " setCount(min);", + " },", + " togglePause: () => {", + " setPaused(!paused);", + " },", + " };", + "};" + ], + "description": "React hook to create a countup functionality" + }, + "useDebounce": { + "prefix": "useDebounce", + "body": [ + "import { useEffect, useState } from 'react';", + "", + "export const useDebounce = (value: T, delay: number) => {", + " const [debouncedValue, setDebouncedValue] = useState(value);", + "", + " useEffect(() => {", + " const handleTimeout = setTimeout(() => {", + " setDebouncedValue(value);", + " }, delay);", + "", + " return () => {", + " clearTimeout(handleTimeout);", + " };", + " }, [value, delay]);", + "", + " return debouncedValue;", + "};" + ], + "description": "React hook to delay the execution of function or state update" + }, + "useDownload": { + "prefix": "useDownload", + "body": [ + "import { useState } from 'react';", + "", + "export const useDownload = () => {", + " const [error, setError] = useState(null);", + " const [isDownloading, setIsDownloading] = useState(false);", + " const [progress, setProgress] = useState(null);", + "", + " const handleResponse = async (response: Response): Promise => {", + " if (!response.ok) {", + " throw new Error('Could not download file');", + " }", + "", + " const contentLength = response.headers.get('content-length');", + " const reader = response.clone().body?.getReader();", + "", + " if (!contentLength || !reader) {", + " const blob = await response.blob();", + "", + " return createBlobURL(blob);", + " }", + "", + " const stream = await getStream(contentLength, reader);", + " const newResponse = new Response(stream);", + " const blob = await newResponse.blob();", + "", + " return createBlobURL(blob);", + " };", + "", + " const getStream = async (", + " contentLength: string,", + " reader: ReadableStreamDefaultReader", + " ): Promise> => {", + " let loaded = 0;", + " const total = parseInt(contentLength, 10);", + "", + " return new ReadableStream({", + " async start(controller) {", + " try {", + " for (;;) {", + " const { done, value } = await reader.read();", + "", + " if (done) break;", + "", + " loaded += value.byteLength;", + " const percentage = Math.trunc((loaded / total) * 100);", + " setProgress(percentage);", + " controller.enqueue(value);", + " }", + " } catch (error) {", + " controller.error(error);", + " throw error;", + " } finally {", + " controller.close();", + " }", + " },", + " });", + " };", + "", + " const createBlobURL = (blob: Blob): string => {", + " return window.URL.createObjectURL(blob);", + " };", + "", + " const handleDownload = (fileName: string, url: string) => {", + " const link = document.createElement('a');", + "", + " link.href = url;", + " link.setAttribute('download', fileName);", + " document.body.appendChild(link);", + " link.click();", + " document.body.removeChild(link);", + " window.URL.revokeObjectURL(url);", + " };", + "", + " const downloadFile = async (fileName: string, fileUrl: string) => {", + " setIsDownloading(true);", + " setError(null);", + " setProgress(null);", + "", + " try {", + " const response = await fetch(fileUrl);", + " const url = await handleResponse(response);", + "", + " handleDownload(fileName, url);", + " } catch (error) {", + " setError(error);", + " } finally {", + " setIsDownloading(false);", + " }", + " };", + "", + " return {", + " error,", + " isDownloading,", + " progress,", + " downloadFile,", + " };", + "};" + ], + "description": "React hook to download a file" + }, + "useEventListener": { + "prefix": "useEventListener", + "body": [ + "import { useEffect, useRef } from 'react';", + "", + "export default function useEventListener(", + " eventName: string,", + " callback: EventListener,", + " element: HTMLElement | (Window & typeof globalThis) | Document | null = window", + ") {", + " const callbackRef = useRef(callback);", + "", + " useEffect(() => {", + " callbackRef.current = callback;", + " }, [callback]);", + "", + " useEffect(() => {", + " if (!(element && element.addEventListener)) {", + " return;", + " }", + "", + " const eventListener = (event: Event) => callbackRef.current(event);", + "", + " element.addEventListener(eventName, eventListener);", + "", + " return () => {", + " element.removeEventListener(eventName, eventListener);", + " };", + " }, [eventName, element]);", + "}" + ], + "description": "React hook to listen for events on a target element" + }, + "useFavicon": { + "prefix": "useFavicon", + "body": [ + "import { useEffect, useState } from 'react';", + "", + "export const useFavicon = (): ((newFavicon: string) => void) => {", + " const [faviconUrl, setFaviconUrl] = useState('');", + "", + " useEffect(() => {", + " let link = document.querySelector(`link[rel~='icon']`) as HTMLLinkElement;", + "", + " if (!link) {", + " link = document.createElement('link');", + " link.type = 'image/x-icon';", + " link.rel = 'icon';", + " document.head.appendChild(link);", + " }", + "", + " link.href = faviconUrl;", + " }, [faviconUrl]);", + "", + " const changeFavicon = (newFavicon: string) => setFaviconUrl(newFavicon);", + "", + " return changeFavicon;", + "};" + ], + "description": "React hook to change the page favicon" + }, + "useFetch": { + "prefix": "useFetch", + "body": [ + "import { useState, useEffect } from 'react';", + "", + "// Define the interface for the data returned by the API.", + "interface Data {}", + "", + "// Define the interface for the error returned by the API.", + "interface Error {}", + "", + "export const useFetch = (url: string, reqOpt?: RequestInit) => {", + " const [data, setData] = useState();", + " const [error, setError] = useState();", + " const [isLoading, setIsLoading] = useState(false);", + " const [isSuccess, setIsSuccess] = useState(false);", + "", + " const fetchData = async () => {", + " setIsLoading(true);", + "", + " try {", + " const res = await fetch(url, reqOpt && reqOpt);", + " const data = await res.json();", + "", + " if (res.status === 200) {", + " setIsSuccess(true);", + " setData(data);", + " setError(undefined);", + " } else {", + " setIsSuccess(false);", + " setError(data);", + " setData(undefined);", + " }", + " } catch (e) {", + " setIsSuccess(false);", + " setData(undefined);", + " if (e instanceof Error) {", + " setError(e);", + " }", + " }", + "", + " setIsLoading(false);", + " };", + "", + " useEffect(() => {", + " fetchData();", + " }, []);", + "", + " const refetch = () => fetchData();", + "", + " return { data, error, isLoading, isError: !isSuccess, isSuccess, refetch };", + "};" + ], + "description": "React hook to fetch data from an API" + }, + "useFirstRender": { + "prefix": "useFirstRender", + "body": [ + "import { useRef, useEffect } from 'react';", + "", + "export const useFirstRender = () => {", + " const firstRender = useRef(true);", + "", + " useEffect(() => {", + " firstRender.current = false;", + " }, []);", + "", + " return firstRender.current;", + "};" + ], + "description": "React hook to detect if it is the first render of a component" + }, + "useFirstVisit": { + "prefix": "useFirstVisit", + "body": [ + "import { useState, useEffect } from 'react';", + "", + "export const useFirstVisit = (): boolean => {", + " const [isFirstVisit, setIsFirstVisit] = useState(false);", + "", + " useEffect(() => {", + " const firstVisit = localStorage.getItem('firstVisit');", + "", + " if (firstVisit === null) {", + " localStorage.setItem('firstVisit', 'false');", + " setIsFirstVisit(true);", + " }", + " }, []);", + "", + " return isFirstVisit;", + "};" + ], + "description": "React hook to detect if it is the user's first visit" + }, + "useGeolocation": { + "prefix": "useGeolocation", + "body": [ + "import { useState } from 'react';", + "", + "interface Payload {", + " lat: number;", + " lng: number;", + "}", + "", + "export function useGeolocation(defaultPosition: Payload | null = null) {", + " const [isLoading, setIsLoading] = useState(false);", + " const [position, setPosition] = useState(defaultPosition);", + " const [error, setError] = useState(null);", + "", + " function getPosition(): void {", + " if (!navigator.geolocation)", + " return setError('Your browser does not support geolocation');", + "", + " setIsLoading(true);", + " navigator.geolocation.getCurrentPosition(", + " (pos) => {", + " setPosition({", + " lat: pos.coords.latitude,", + " lng: pos.coords.longitude,", + " });", + " setIsLoading(false);", + " },", + " (error) => {", + " setError(error.message);", + " setIsLoading(false);", + " }", + " );", + " }", + "", + " return { isLoading, position, error, getPosition };", + "}" + ], + "description": "React hook to get user's latitude and longitude" + }, + "useHover": { + "prefix": "useHover", + "body": [ + "import { useState, RefObject, useEffect } from 'react';", + "", + "export const useHover = (ref: RefObject) => {", + " const [isHovered, setIsHovered] = useState(false);", + "", + " const handleMouseEnter = () => setIsHovered(true);", + " const handleMouseLeave = () => setIsHovered(false);", + "", + " useEffect(() => {", + " const node = ref.current;", + "", + " if (node) {", + " node.addEventListener('mouseenter', handleMouseEnter);", + " node.addEventListener('mouseleave', handleMouseLeave);", + "", + " return () => {", + " node.removeEventListener('mouseenter', handleMouseEnter);", + " node.removeEventListener('mouseleave', handleMouseLeave);", + " };", + " }", + " }, [ref]);", + "", + " return isHovered;", + "};" + ], + "description": "React hook to track when an element is being hovered over" + }, + "useInput": { + "prefix": "useInput", + "body": [ + "import { useState } from 'react';", + "", + "export const useInput = (initialValue: T) => {", + " const [inputValue, setInputValue] = useState(initialValue);", + "", + " const onInputChange = (event: React.ChangeEvent) => {", + " setInputValue(event.target.value as unknown as T);", + " };", + "", + " return { inputValue, onInputChange };", + "};" + ], + "description": "React hook to handle an input element" + }, + "useIsTouchDevice": { + "prefix": "useIsTouchDevice", + "body": [ + "import { useEffect, useState } from 'react';", + "", + "export function useIsTouchDevice() {", + " const [isTouchDevice, setIsTouchDevice] = useState(false);", + "", + " useEffect(() => {", + " function onResize() {", + " setIsTouchDevice(", + " 'ontouchstart' in window ||", + " navigator.maxTouchPoints > 0 ||", + " navigator.maxTouchPoints > 0", + " );", + " }", + "", + " window.addEventListener('resize', onResize);", + " onResize();", + "", + " return () => {", + " window.removeEventListener('resize', onResize);", + " };", + " }, []);", + "", + " return isTouchDevice;", + "}" + ], + "description": "React hook to detect if the user's device is a touch device" + }, + "useKeyPress": { + "prefix": "useKeyPress", + "body": [ + "import { useState, useEffect } from 'react';", + "", + "interface KeyConfig {", + " key: string;", + " ctrl?: boolean;", + " alt?: boolean;", + " shift?: boolean;", + "}", + "", + "export const useKeyPress = (config: KeyConfig) => {", + " const [keyPressed, setKeyPressed] = useState(false);", + " const { key: targetKey, ctrl, alt, shift } = config;", + "", + " const handleKeyDown = (e: KeyboardEvent) => {", + " const { key, ctrlKey, altKey, shiftKey } = e;", + "", + " if (", + " (!ctrl && !alt && !shift && key === targetKey) ||", + " (ctrl && key === targetKey && ctrlKey === ctrl) ||", + " (alt && key === targetKey && altKey === alt) ||", + " (shift && key === targetKey && shiftKey === shift)", + " ) {", + " setKeyPressed(true);", + " }", + " };", + "", + " const handleKeyUp = (e: KeyboardEvent) => {", + " const { key, ctrlKey, altKey, shiftKey } = e;", + "", + " if (", + " (!ctrl && !alt && !shift && key === targetKey) ||", + " (ctrl && key === targetKey && ctrlKey === ctrl) ||", + " (alt && key === targetKey && altKey === alt) ||", + " (shift && key === targetKey && shiftKey === shift)", + " ) {", + " setKeyPressed(false);", + " }", + " };", + "", + " useEffect(() => {", + " window.addEventListener('keydown', handleKeyDown);", + " window.addEventListener('keyup', handleKeyUp);", + "", + " return () => {", + " window.removeEventListener('keydown', handleKeyDown);", + " window.removeEventListener('keyup', handleKeyUp);", + " };", + " }, []);", + "", + " return keyPressed;", + "};" + ], + "description": "React hook to detect when a specific key or a combination of keys is pressed or released" + }, + "useLang": { + "prefix": "useLang", + "body": [ + "import { useSyncExternalStore } from 'react';", + "", + "const langSubscribe = (cb: () => void) => {", + " window.addEventListener('languagechange', cb);", + " return () => window.removeEventListener('languagechange', cb);", + "};", + "", + "const getLang = () => navigator.language;", + "", + "export const useLang = (): string =>", + " useSyncExternalStore(langSubscribe, getLang);" + ], + "description": "React hook to detect the language selected in the browser" + }, + "useLocalStorage": { + "prefix": "useLocalStorage", + "body": [ + "import { useEffect, useSyncExternalStore, useCallback } from 'react';", + "", + "const isFunction = (", + " value: T | ((prevState: T) => T)", + "): value is (prevState: T) => T => typeof value === 'function';", + "", + "const dispatchStorageEvent = (key: string, newValue: string | null) =>", + " window.dispatchEvent(new StorageEvent('storage', { key, newValue }));", + "", + "const getLocalStorageItem = (key: string) => window.localStorage.getItem(key);", + "", + "const setLocalStorageItem = (key: string, value: T) => {", + " const stringifiedValue = JSON.stringify(value);", + " window.localStorage.setItem(key, stringifiedValue);", + " dispatchStorageEvent(key, stringifiedValue);", + "};", + "", + "const removeLocalStorageItem = (key: string) => {", + " window.localStorage.removeItem(key);", + " dispatchStorageEvent(key, null);", + "};", + "", + "const localStorageSubscribe = (cb: () => void) => {", + " window.addEventListener('storage', cb);", + " return () => window.removeEventListener('storage', cb);", + "};", + "", + "export const useLocalStorage = (key: string, initialValue: T) => {", + " const getSnapshot = () => getLocalStorageItem(key);", + " const store = useSyncExternalStore(localStorageSubscribe, getSnapshot);", + "", + " const setState = useCallback(", + " (v: T) => {", + " try {", + " let nextState: T;", + " if (isFunction(v)) {", + " const parsedStore = store ? JSON.parse(store) : null;", + " nextState = v(parsedStore ?? initialValue);", + " } else {", + " nextState = v;", + " }", + "", + " if (nextState === undefined || nextState === null) {", + " removeLocalStorageItem(key);", + " } else {", + " setLocalStorageItem(key, nextState);", + " }", + " } catch (e) {", + " console.log(e);", + " }", + " },", + " [key, store, initialValue]", + " );", + "", + " useEffect(() => {", + " if (", + " getLocalStorageItem(key) === null &&", + " typeof initialValue !== 'undefined'", + " ) {", + " setLocalStorageItem(key, initialValue);", + " }", + " }, [key, initialValue]);", + "", + " return {", + " current: store ? JSON.parse(store) : initialValue,", + " setItemValue: setState,", + " removeItem: () => removeLocalStorageItem(key),", + " };", + "};" + ], + "description": "React hook to store, retrieve and delete data from the browser's localStorage API" + }, + "useNavigatorShare": { + "prefix": "useNavigatorShare", + "body": [ + "interface IShareData {", + " title: string;", + " text: string;", + " url?: string;", + " files?: File[];", + "}", + "", + "const errorMessages: Record = {", + " NotAllowedError: 'Permission to share denied.',", + " AbortError: 'The sharing action was aborted.',", + " NotSupportedError: 'Your browser does not support the sharing feature.',", + " TypeError: 'Error while sharing: incorrect data type.',", + "};", + "", + "function checkPermission(files?: File[]) {", + " if (!navigator.canShare) {", + " throw new Error('Your browser does not support the sharing feature.');", + " }", + "", + " if (!navigator.canShare({ files } || { files: [new File([], '')] })) {", + " throw new Error(", + " `Your browser does not allow sharing ${", + " files ? 'this type of ' : ''", + " } files.`", + " );", + " }", + "}", + "", + "function surroundTryCatch(fn: (data: IShareData) => void | Promise) {", + " return async (data: IShareData) => {", + " try {", + " await fn(data);", + " } catch (error: unknown) {", + " if ((error as Error).name in errorMessages) {", + " const message = `Error while sharing: ${", + " errorMessages[(error as Error).name]", + " }`;", + " console.error(message);", + " } else {", + " throw error;", + " }", + " }", + " };", + "}", + "", + "export const useNavigatorShare = () => {", + " async function shareInNavigator(data: IShareData) {", + " if (data.files) checkPermission(data.files);", + "", + " await navigator.share({", + " title: data.title,", + " text: data.text ?? '',", + " url: data.url ?? '',", + " files: data.files ?? [],", + " });", + " }", + "", + " return {", + " shareInNavigator: surroundTryCatch(shareInNavigator),", + " };", + "};" + ], + "description": "React hook to share content through the browser" + }, + "useOffline": { + "prefix": "useOffline", + "body": [ + "import { useEffect, useState } from 'react';", + "", + "type TUseOffline = () => boolean;", + "", + "export const useOffline: TUseOffline = () => {", + " const [offline, setOffline] = useState(null);", + "", + " useEffect(() => {", + " const handleNetworkState = () => {", + " setOffline(!offline);", + " };", + " addEventListener('offline', handleNetworkState);", + " addEventListener('online', handleNetworkState);", + "", + " return () => {", + " removeEventListener('online', handleNetworkState);", + " removeEventListener('offline', handleNetworkState);", + " };", + " }, [offline]);", + "", + " return !!offline;", + "};" + ], + "description": "Reaction hook to track if user is offline or not" + }, + "useOnScreen": { + "prefix": "useOnScreen", + "body": [ + "import { RefObject, useEffect, useState } from 'react';", + "", + "export default function useOnScreen(", + " ref: RefObject,", + " rootMargin = '0px'", + "): boolean {", + " const [isIntersecting, setIntersecting] = useState(false);", + "", + " useEffect(() => {", + " const observer = new IntersectionObserver(", + " ([entry]) => setIntersecting(entry.isIntersecting),", + " { rootMargin }", + " );", + "", + " if (ref.current) {", + " observer.observe(ref.current);", + " }", + "", + " return () => {", + " observer.disconnect();", + " };", + " }, [ref, rootMargin]);", + "", + " return isIntersecting;", + "}" + ], + "description": "React hook to monitor whether a referenced element is visible on the screen" + }, + "useOutsideClick": { + "prefix": "useOutsideClick", + "body": [ + "import { useEffect, MutableRefObject } from 'react';", + "", + "export const useOutsideClick = (", + " ref: MutableRefObject,", + " fn: () => void", + ") => {", + " useEffect(() => {", + " const handleClickOutside = (event: MouseEvent) => {", + " if (ref.current && !ref.current.contains(event.target as Node)) {", + " fn();", + " }", + " };", + "", + " document.addEventListener('click', handleClickOutside);", + "", + " return () => {", + " document.removeEventListener('click', handleClickOutside);", + " };", + " }, [ref, fn]);", + "};" + ], + "description": "React hook to detect clicks outside of a specified component" + }, + "usePrevious": { + "prefix": "usePrevious", + "body": [ + "import { useRef } from 'react';", + "", + "export default function usePrevious(value: T): T | undefined {", + " const currentRef = useRef(value);", + " const previousRef = useRef();", + "", + " if (currentRef.current !== value) {", + " previousRef.current = currentRef.current;", + " currentRef.current = value;", + " }", + "", + " return previousRef.current;", + "}" + ], + "description": "React hook to track the previous value of a variable" + }, + "useRandomColor": { + "prefix": "useRandomColor", + "body": [ + "import { useState } from 'react';", + "", + "export const useRandomColor = () => {", + " const [color, setColor] = useState('#000000');", + "", + " const generateColor = () => {", + " const newColor =", + " '#' +", + " Math.floor(Math.random() * 16777215)", + " .toString(16)", + " .padStart(6, '0');", + "", + " setColor(newColor);", + " return newColor;", + " };", + "", + " return { color, generateColor };", + "};" + ], + "description": "React hook to generate random colors" + }, + "useScript": { + "prefix": "useScript", + "body": [ + "import { useEffect, useState } from 'react';", + "", + "export const useScript = (url: string) => {", + " const [loading, setLoading] = useState(true);", + " const [error, setError] = useState(null);", + "", + " useEffect(() => {", + " const script = document.createElement('script');", + " script.src = url;", + " script.async = true;", + "", + " script.onload = () => {", + " setLoading(false);", + " };", + "", + " script.onerror = () => {", + " setError(`Failed to load script ${url}`);", + " setLoading(false);", + " };", + "", + " document.body.appendChild(script);", + "", + " return () => {", + " document.body.removeChild(script);", + " };", + " }, [url]);", + "", + " return { loading, error };", + "};" + ], + "description": "React hook to load a script" + }, + "useScroll": { + "prefix": "useScroll", + "body": [ + "import { useState, useLayoutEffect } from 'react';", + "", + "export const useScroll = () => {", + " const [position, setPosition] = useState({", + " x: 0,", + " y: 0,", + " });", + "", + " const handleScroll = () => {", + " setPosition({", + " x: window.scrollX,", + " y: window.scrollY,", + " });", + " };", + "", + " useLayoutEffect(() => {", + " window.addEventListener('scroll', handleScroll);", + "", + " return () => {", + " window.removeEventListener('scroll', handleScroll);", + " };", + " }, []);", + "", + " return { position, scrollTo: window.scrollTo };", + "};" + ], + "description": "React hook to track and manipule the scroll position of a web page" + }, + "useSearchParams": { + "prefix": "useSearchParams", + "body": [ + "import { useEffect, useState } from 'react';", + "", + "/* eslint-disable-next-line */", + "type TUseSearchParams = >(", + " url?: string,", + " opt?: { unique: boolean }", + ") => T;", + "", + "export const useSearchParams: TUseSearchParams = (", + " url = location.href,", + " opt = { unique: true }", + ") => {", + " const _urlSearch = new URL(url);", + " const [params, setParams] = useState>(() =>", + " Object.fromEntries(_urlSearch.searchParams.entries())", + " );", + "", + " useEffect(() => {", + " const len: number = Object.values(params).length;", + "", + " if (!opt || opt.unique || len === _urlSearch.searchParams?.size) return;", + "", + " for (const [key, value] of _urlSearch.searchParams) {", + " if (value === params?.[key]) continue;", + " if (", + " Array.isArray(params?.[key]) &&", + " Array.from(params?.[key]).includes(value)", + " )", + " continue;", + " setParams(() => ({", + " ...params,", + " [key]: [...(params?.[key] ?? []), value],", + " }));", + " }", + " }, []);", + "", + " return Object.fromEntries(", + " Object.entries(params).map(([key, value]) => [", + " key,", + " !Array.isArray(value)", + " ? JSON.parse(value)", + " : value.map((items) => JSON.parse(items)),", + " ])", + " ) as T;", + "};" + ], + "description": "React hook to extract search parameters from URL" + }, + "useStopwatch": { + "prefix": "useStopwatch", + "body": [ + "import { useState, useEffect } from 'react';", + "", + "const addLeadingZero = (digit: number): string => {", + " let timeStr = '';", + "", + " digit % 10 === digit ? (timeStr += `0${digit}`) : (timeStr += `${digit}`);", + "", + " return timeStr;", + "};", + "", + "interface Stopwatch {", + " current: string;", + " isPaused: boolean;", + " isOver: boolean;", + " currentDays: number;", + " currentHours: number;", + " currentMinutes: number;", + " currentSeconds: number;", + " elapsedSeconds: number;", + " pause: () => void;", + " play: () => void;", + " reset: () => void;", + " togglePause: () => void;", + "}", + "", + "export const useStopwatch = (): Stopwatch => {", + " const [time, setTime] = useState({", + " days: 0,", + " hours: 0,", + " minutes: 0,", + " seconds: 0,", + " });", + " const [paused, setPaused] = useState(false);", + " const divider = ':';", + " const [isOver, setIsOver] = useState(false);", + "", + " useEffect(() => {", + " if (paused) {", + " return;", + " }", + "", + " const interval = setInterval(() => {", + " setTime((prev) => {", + " let d = prev.days;", + " let h = prev.hours;", + " let m = prev.minutes;", + " let s = prev.seconds;", + "", + " if (s + 1 >= 60) {", + " s = 0;", + " if (m + 1 >= 60) {", + " m = 0;", + " if (h + 1 >= 24) {", + " h = 0;", + " d++;", + " } else {", + " h++;", + " }", + " } else {", + " m++;", + " }", + " } else {", + " s++;", + " }", + "", + " return { days: d, hours: h, minutes: m, seconds: s };", + " });", + " }, 1000);", + "", + " return () => clearInterval(interval);", + " }, [time, paused]);", + "", + " return {", + " current: `${addLeadingZero(time.days)}${divider}${addLeadingZero(", + " time.hours", + " )}${divider}${addLeadingZero(time.minutes)}${divider}${addLeadingZero(", + " time.seconds", + " )}`,", + " isPaused: paused,", + " isOver,", + " currentDays: time.days,", + " currentHours: time.hours,", + " currentMinutes: time.minutes,", + " currentSeconds: time.seconds,", + " elapsedSeconds:", + " time.days * 86400 + time.hours * 3600 + time.minutes * 60 + time.seconds,", + " pause: () => setPaused(true),", + " play: () => setPaused(false),", + " reset: () => {", + " setIsOver(false);", + " setTime({ days: 0, hours: 0, minutes: 0, seconds: 0 });", + " },", + " togglePause: () => {", + " setPaused(!paused);", + " },", + " };", + "};" + ], + "description": "React hook to create a stopwatch functionality" + }, + "useTimer": { + "prefix": "useTimer", + "body": [ + "import { useState, useEffect } from 'react';", + "", + "const parseTime = (time: string) => {", + " const splitTime = time.split(':');", + "", + " const [days, hours, minutes, seconds] = splitTime.map((value) =>", + " Number(value)", + " );", + "", + " return { days, hours, minutes, seconds };", + "};", + "", + "const addLeadingZero = (digit: number): string => {", + " let timeStr = '';", + "", + " digit % 10 === digit ? (timeStr += `0${digit}`) : (timeStr += `${digit}`);", + "", + " return timeStr;", + "};", + "", + "interface Timer {", + " current: string;", + " isPaused: boolean;", + " isOver: boolean;", + " currentDays: number;", + " currentHours: number;", + " currentMinutes: number;", + " currentSeconds: number;", + " elapsedSeconds: number;", + " remainingSeconds: number;", + " pause: () => void;", + " play: () => void;", + " reset: () => void;", + " togglePause: () => void;", + "}", + "", + "export const useTimer = (startTime: string): Timer => {", + " const { days, hours, minutes, seconds } = parseTime(startTime);", + " const [time, setTime] = useState({ days, hours, minutes, seconds });", + " const [paused, setPaused] = useState(false);", + " const divider = ':';", + " const [isOver, setIsOver] = useState(false);", + "", + " useEffect(() => {", + " if (paused) {", + " return;", + " }", + "", + " const interval = setInterval(() => {", + " setTime((prev) => {", + " let d = prev.days;", + " let h = prev.hours;", + " let m = prev.minutes;", + " let s = prev.seconds;", + "", + " if (s - 1 < 0) {", + " s = 59;", + " if (m - 1 < 0) {", + " m = 59;", + " if (h - 1 < 0) {", + " h = 23;", + " if (d - 1 >= 0) {", + " d--;", + " }", + " } else {", + " h--;", + " }", + " } else {", + " m--;", + " }", + " } else {", + " s--;", + " }", + "", + " return { days: d, hours: h, minutes: m, seconds: s };", + " });", + " }, 1000);", + "", + " if (", + " time.seconds === 0 &&", + " time.minutes === 0 &&", + " time.hours === 0 &&", + " time.days === 0", + " ) {", + " setIsOver(true);", + " clearInterval(interval);", + " return;", + " }", + "", + " return () => clearInterval(interval);", + " }, [days, hours, minutes, seconds, time, paused]);", + "", + " return {", + " current: `${addLeadingZero(time.days)}${divider}${addLeadingZero(", + " time.hours", + " )}${divider}${addLeadingZero(time.minutes)}${divider}${addLeadingZero(", + " time.seconds", + " )}`,", + " isPaused: paused,", + " isOver,", + " currentDays: time.days,", + " currentHours: time.hours,", + " currentMinutes: time.minutes,", + " currentSeconds: time.seconds,", + " elapsedSeconds:", + " days * 86400 +", + " hours * 3600 +", + " minutes * 60 +", + " seconds -", + " (time.days * 86400 +", + " time.hours * 3600 +", + " time.minutes * 60 +", + " time.seconds),", + " remainingSeconds:", + " time.days * 86400 + time.hours * 3600 + time.minutes * 60 + time.seconds,", + " pause: () => setPaused(true),", + " play: () => setPaused(false),", + " reset: () => {", + " setIsOver(false);", + " setTime({ days, hours, minutes, seconds });", + " },", + " togglePause: () => {", + " setPaused(!paused);", + " },", + " };", + "};" + ], + "description": "React hook to create a timer functionality" + }, + "useTitle": { + "prefix": "useTitle", + "body": [ + "import { useState, useEffect } from 'react';", + "", + "interface UseTitleOutput {", + " title: string;", + " changeTitle: (newTitle: string) => void;", + "}", + "", + "export const useTitle = (): UseTitleOutput => {", + " const [title, setTitle] = useState(document.title);", + "", + " useEffect(() => {", + " document.title = title;", + " }, [title]);", + "", + " const changeTitle = (newTitle: string) => setTitle(newTitle);", + "", + " return { title, changeTitle };", + "};" + ], + "description": "React hook to change the page title" + }, + "useToggle": { + "prefix": "useToggle", + "body": [ + "import { useState } from 'react';", + "", + "export const useToggle = (initialValue: boolean) => {", + " const [current, setCurrent] = useState(initialValue);", + "", + " const handleToggle = () => setCurrent((prev) => !prev);", + "", + " return { current, handleToggle };", + "};" + ], + "description": "React hook to toggle a boolean value" + }, + "useVideo": { + "prefix": "useVideo", + "body": [ + "import { useEffect, useState, RefObject } from 'react';", + "", + "export const useVideo = (ref: RefObject) => {", + " const video = ref.current;", + "", + " const [videoState, setVideoState] = useState({", + " isPaused: video ? video?.paused : true,", + " isMuted: video ? video?.muted : false,", + " currentVolume: video ? video?.volume : 100,", + " currentTime: video ? video?.currentTime : 0,", + " });", + "", + " const play = () => {", + " video?.play();", + " setVideoState((prev) => {", + " return {", + " ...prev,", + " isPaused: false,", + " isMuted: video ? video.muted : prev.isMuted,", + " };", + " });", + " };", + "", + " const pause = () => {", + " video?.pause();", + " setVideoState((prev) => {", + " return {", + " ...prev,", + " isPaused: true,", + " };", + " });", + " };", + "", + " const handlePlayPauseControl = (e: Event) => {", + " setVideoState((prev) => {", + " return {", + " ...prev,", + " isPaused: (e.target as HTMLVideoElement).paused,", + " };", + " });", + " };", + "", + " const togglePause = () => (video?.paused ? play() : pause());", + "", + " const handleVolume = (delta: number) => {", + " const deltaDecimal = delta / 100;", + "", + " if (video) {", + " let newVolume = video?.volume + deltaDecimal;", + "", + " if (newVolume >= 1) {", + " newVolume = 1;", + " } else if (newVolume <= 0) {", + " newVolume = 0;", + " }", + "", + " video.volume = newVolume;", + " setVideoState((prev) => {", + " return {", + " ...prev,", + " currentVolume: newVolume * 100,", + " };", + " });", + " }", + " };", + "", + " const handleVolumeControl = (e: Event) => {", + " if (e.target && video) {", + " const newVolume = (e.target as HTMLVideoElement).volume * 100;", + "", + " if (newVolume === videoState.currentVolume) {", + " handleMute(video.muted);", + " return;", + " }", + "", + " setVideoState((prev) => ({", + " ...prev,", + " currentVolume: (e.target as HTMLVideoElement).volume * 100,", + " }));", + " }", + " };", + "", + " const handleMute = (mute: boolean) => {", + " if (video) {", + " video.muted = mute;", + " setVideoState((prev) => {", + " return {", + " ...prev,", + " isMuted: mute,", + " };", + " });", + " }", + " };", + "", + " const handleTime = (delta: number = 5) => {", + " if (video) {", + " let newTime = video.currentTime + delta;", + "", + " if (newTime >= video.duration) {", + " newTime = video.duration;", + " } else if (newTime <= 0) {", + " newTime = 0;", + " }", + "", + " video.currentTime = newTime;", + " setVideoState((prev) => {", + " return {", + " ...prev,", + " currentTime: newTime,", + " };", + " });", + " }", + " };", + "", + " const handleTimeControl = (e: Event) => {", + " setVideoState((prev) => {", + " return {", + " ...prev,", + " currentTime: (e.target as HTMLVideoElement).currentTime,", + " };", + " });", + " };", + "", + " const toggleFullscreen = () => {", + " if (!document.fullscreenElement) {", + " video?.requestFullscreen().catch((err) => {", + " console.log(err);", + " });", + " } else {", + " document.exitFullscreen();", + " }", + " };", + "", + " useEffect(() => {", + " return () => {", + " pause();", + " };", + " }, []);", + "", + " useEffect(() => {", + " if (video) {", + " video.addEventListener('volumechange', handleVolumeControl);", + " video.addEventListener('play', handlePlayPauseControl);", + " video.addEventListener('pause', handlePlayPauseControl);", + " video.addEventListener('timeupdate', handleTimeControl);", + "", + " return () => {", + " video.removeEventListener('volumechange', handleVolumeControl);", + " video.removeEventListener('play', handlePlayPauseControl);", + " video.removeEventListener('pause', handlePlayPauseControl);", + " video.removeEventListener('timeupdate', handleTimeControl);", + " };", + " }", + " }, [video]);", + "", + " return {", + " ...videoState,", + " play,", + " pause,", + " togglePause,", + " increaseVolume: (increase: number = 5) => handleVolume(increase),", + " decreaseVolume: (decrease: number = 5) => handleVolume(decrease * -1),", + " mute: () => handleMute(true),", + " unmute: () => handleMute(false),", + " toggleMute: () => handleMute(!video?.muted),", + " forward: (increase: number = 5) => handleTime(increase),", + " back: (decrease: number = 5) => handleTime(decrease * -1),", + " toggleFullscreen,", + " };", + "};" + ], + "description": "React hook to manage a video" + }, + "useWindowSize": { + "prefix": "useWindowSize", + "body": [ + "import { useState, useEffect } from 'react';", + "", + "type WindowSize = {", + " width: number;", + " height: number;", + "};", + "", + "export const useWindowSize = (): WindowSize => {", + " const [windowSize, setWindowSize] = useState({", + " width: window.innerWidth,", + " height: window.innerHeight,", + " });", + "", + " const handleResize = () => {", + " setWindowSize({", + " width: window.innerWidth,", + " height: window.innerHeight,", + " });", + " };", + "", + " useEffect(() => {", + " window.addEventListener('resize', handleResize);", + "", + " return () => window.removeEventListener('resize', handleResize);", + " }, []);", + "", + " return windowSize;", + "};" + ], + "description": "React hook to track the dimensions of the browser window" + } +}