From ed60c8f5636c91bd521b71db0a87a48e2801a60f Mon Sep 17 00:00:00 2001 From: Dhruv Saxena Date: Mon, 16 Dec 2024 19:09:07 +0530 Subject: [PATCH 1/3] added useCopyToClipboard hook and refactored codebase --- src/hooks/useCopyToClipboard.js | 58 ++++++++++++++++++++++++++ src/hooks/useDebouncedValue.js | 2 +- src/hooks/useInterval.js | 27 +++++++++++- src/hooks/useIsomorphicLayoutEffect.js | 4 ++ src/hooks/useTimeout.js | 42 +++++++++++++++---- src/index.js | 8 ++-- 6 files changed, 126 insertions(+), 15 deletions(-) create mode 100644 src/hooks/useCopyToClipboard.js create mode 100644 src/hooks/useIsomorphicLayoutEffect.js diff --git a/src/hooks/useCopyToClipboard.js b/src/hooks/useCopyToClipboard.js new file mode 100644 index 0000000..5fc86c2 --- /dev/null +++ b/src/hooks/useCopyToClipboard.js @@ -0,0 +1,58 @@ +import { useCallback, useState } from "react"; + +/** + * + * @description Custom hook that copies text to the clipboard using the `Clipboard API`. + * + * + * @typedef {string | null} CopiedValue + * @typedef {(text: string) => Promise} CopyFunction + * + * @returns {[CopiedValue, CopyFunction]} + * + * @example + * ```jsx + * + * const [copiedText, copyFunction] = useCopyToClipboard() + * + * const handleCopy = async () => { + * const copyStatus = await copyFunction("Hello, World!") + * if(copyStatus) console.log("Text copied successfully!") + * else console.log("Copy failed!") + * } + * + * + * ``` + */ +export function useCopyToClipboard() { + const [copiedText, setCopiedText] = useState(null); + + /** + * @description Function that copies text to the clipboard. + * @type {CopyFunction} + */ + const copyFunction = useCallback( + async (text) => { + if (!navigator.clipboard) { + // Clipboard API not available + console.warn("Clipboard API not available"); + return false; + } + + try { + await navigator.clipboard.writeText(text); + setCopiedText(text); + + return true; + } catch (error) { + console.warn("Copy to clipboard failed", error); + setCopiedText(null); + + return false; + } + }, + [] + ); + + return [copiedText, copyFunction]; +} diff --git a/src/hooks/useDebouncedValue.js b/src/hooks/useDebouncedValue.js index bad2353..15313c9 100644 --- a/src/hooks/useDebouncedValue.js +++ b/src/hooks/useDebouncedValue.js @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useEffect, useState } from 'react'; export function useDebouncedValue(value, delay) { const [debouncedValue, setDebouncedValue] = useState(value); diff --git a/src/hooks/useInterval.js b/src/hooks/useInterval.js index db5cbb2..330df84 100644 --- a/src/hooks/useInterval.js +++ b/src/hooks/useInterval.js @@ -1,8 +1,33 @@ -import { useEffect, useRef } from 'react' +import { useEffect, useRef } from 'react'; +import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'; +/** + * @description Custom hook that creates an interval that invokes a callback function at a specified delay using the `setInterval` API. + * + * + * @param {() => void} callback The function to be invoked at each interval. + * @param {number | null} delay The time, in milliseconds, between each invocation of the callback. Use null to clear the interval. + * + * @returns {void} This hook does not return anything. + * + * @example + * ```jsx + * const [count, setCount] = useState(0) + * const counter = () => { + * setCount(x => x + 1) + * } + * // Increment the count every 2 seconds + * useInterval(counter, 2000) + * ``` + */ export function useInterval(callback, delay) { const savedCallback = useRef(callback) + // Remember the latest callback if it changes. + useIsomorphicLayoutEffect(() => { + savedCallback.current = callback; + }, [callback]); + useEffect(() => { if (delay === null) { return diff --git a/src/hooks/useIsomorphicLayoutEffect.js b/src/hooks/useIsomorphicLayoutEffect.js new file mode 100644 index 0000000..49ca31f --- /dev/null +++ b/src/hooks/useIsomorphicLayoutEffect.js @@ -0,0 +1,4 @@ +import { useEffect, useLayoutEffect } from 'react' + +export const useIsomorphicLayoutEffect = + typeof window !== 'undefined' ? useLayoutEffect : useEffect diff --git a/src/hooks/useTimeout.js b/src/hooks/useTimeout.js index 101dd20..cbf0183 100644 --- a/src/hooks/useTimeout.js +++ b/src/hooks/useTimeout.js @@ -1,19 +1,43 @@ -import { useEffect, useRef } from 'react' +import { useEffect, useRef } from "react"; +import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect"; +/** + * @description Custom hook that handles timeouts in React components using the setTimeout API. + * + * + * @param {() => void} callback The function to be executed when the timeout elapses. + * @param {number | null} delay The duration (in milliseconds) for the timeout. Set to null to clear the timeout. + * + * @returns {void} This hook does not return anything. + * + * @example + * ```jsx + * const [visible, setVisible] = useState(true) + * const hide = () => { + * setVisible(false) + * } + * useTimeout(hide, 5000) + * ``` + */ export function useTimeout(callback, delay) { - const savedCallback = useRef(callback) + const savedCallback = useRef(callback); + + // Remember the latest callback if it changes. + useIsomorphicLayoutEffect(() => { + savedCallback.current = callback; + }, [callback]); useEffect(() => { if (!delay && delay !== 0) { - return + return; } const id = setTimeout(() => { - savedCallback.current() - }, delay) + savedCallback.current(); + }, delay); return () => { - clearTimeout(id) - } - }, [delay]) -} \ No newline at end of file + clearTimeout(id); + }; + }, [delay]); +} diff --git a/src/index.js b/src/index.js index 0f45f06..283a3f7 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,5 @@ -import { useInterval } from "./hooks/useInterval"; -import { useTimeout } from "./hooks/useTimeout"; -import { useDebouncedValue } from "./hooks/useDebouncedValue"; +export { useDebouncedValue } from "./hooks/useDebouncedValue"; +export { useInterval } from "./hooks/useInterval"; +export { useIsomorphicLayoutEffect } from "./hooks/useIsomorphicLayoutEffect"; +export { useTimeout } from "./hooks/useTimeout"; -export {useInterval,useTimeout,useDebouncedValue} \ No newline at end of file From 3705bb95fbb47aa82dd89f8d037aaaa5ff392a89 Mon Sep 17 00:00:00 2001 From: Dhruv Saxena Date: Mon, 16 Dec 2024 19:16:48 +0530 Subject: [PATCH 2/3] updated readme.md file, added documentation and fixed syntax highlighting --- README.md | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8b5a1c6..876516d 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ A lightweight library that provides two essential custom hooks for your react or - **`useInterval`**: Set up recurring functions at specified intervals. - **`useTimeout`**: Delay execution of a function for a specified time. - **`useDebouncedValue`**: Debounce a value, updating it after a specified delay. +- **`useCopyToClipboard`**: Copies text to clipboard ## Installation @@ -22,7 +23,7 @@ npm install @shurutech/react-hook-tools #### Syntax: -``` +```jsx useInterval(()=>{ // functionality },time) @@ -31,7 +32,7 @@ Note: Time will in milliseconds ( > 0 ) if you need to run interval, and undefin #### Example -``` +```jsx import { useInterval } from '@shurutech/react-hook-tools'; const IntervalComponent = () => { @@ -47,7 +48,7 @@ const IntervalComponent = () => { #### Syntax: -``` +```jsx useTimeout(()=>{ // functionality },time) @@ -56,7 +57,7 @@ Note: Time will in milliseconds ( > 0 ) if you need to run interval, and undefin #### Example -``` +```jsx import { useTimeout } from '@shurutech/react-hook-tools'; const TimerComponent = () => { @@ -72,13 +73,13 @@ const TimerComponent = () => { #### Syntax: -``` +```jsx const debouncedValue = useDebouncedValue(value, delay); ``` #### Example -``` +```jsx import { useDebouncedValue } from '@shurutech/react-hook-tools'; const SearchComponent = () => { @@ -103,6 +104,37 @@ const SearchComponent = () => { }; ``` +### useCopyToClipboard: + +#### Syntax: + +```jsx +const [copiedText, copyFunction] = useCopyToClipboard() +``` + +#### Example + +```jsx +import { useCopyToClipboard } from '@shurutech/react-hook-tools'; + +const CopyComponent = () => { + const [copiedText, copyFunction] = useCopyToClipboard() + + const handleCopy = async () => { + const copyStatus = await copyFunction("Hello, World!") + + if(copyStatus) + console.log("Text copied successfully!") + else + console.log("Copy failed!") + } + + return ( + + ); +}; +``` + ## How to Contribute 1. Fork this repository to your GitHub account and clone it locally. From a8b06ce6393cc0639927f75f7cdeee1836724a3a Mon Sep 17 00:00:00 2001 From: dhruvshurutech Date: Mon, 16 Dec 2024 19:19:20 +0530 Subject: [PATCH 3/3] exported function from root --- src/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.js b/src/index.js index 283a3f7..7a00e81 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,4 @@ +export { useCopyToClipboard } from './hooks/useCopyToClipboard'; export { useDebouncedValue } from "./hooks/useDebouncedValue"; export { useInterval } from "./hooks/useInterval"; export { useIsomorphicLayoutEffect } from "./hooks/useIsomorphicLayoutEffect";