-
-
Notifications
You must be signed in to change notification settings - Fork 416
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
✨ [Feature] :added new hooks useHash
, useHashHistory
, useClipboard
and useOrigin
#488
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './useClipboard' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { useClipboard } from './useClipboard' | ||
|
||
// mock message | ||
const message = { | ||
content: 'I am comming from openAi response', | ||
} | ||
|
||
export default function CopyToClipboard({ ...props }) { | ||
const { isCopied, copyToClipboard } = useClipboard({ timeout: 1000 }) | ||
|
||
return ( | ||
<div {...props}> | ||
<button | ||
className="h-8 w-8 bg-gray-200 rounded-full flex items-center justify-center" | ||
onClick={() => copyToClipboard(message.content)} | ||
> | ||
{isCopied ? ( | ||
<span className="text-green-500">Copied</span> | ||
) : ( | ||
<span className="text-gray-600">Copy</span> | ||
)} | ||
</button> | ||
</div> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
The useClipboard hook is a custom React hook that facilitates copying text to the clipboard with optional timeout functionality. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { act, renderHook } from '@testing-library/react'; | ||
import { useClipboard } from './useClipboard'; | ||
|
||
|
||
describe('useClipboard()', () => { | ||
it('should copy value to clipboard and reset isCopied state after timeout', async () => { | ||
const { result } = renderHook(() => useClipboard({ timeout: 2000 })); | ||
|
||
act(() => { | ||
result.current.copyToClipboard('test value'); | ||
}); | ||
|
||
// The isCopied state should be true immediately after copying | ||
expect(result.current.isCopied).toBe(true); | ||
|
||
// Fast-forward time by 2000 milliseconds | ||
await new Promise(resolve => setTimeout(resolve, 2000)); | ||
|
||
Comment on lines
+15
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that works when you are fast forwarding time in tests, you should use |
||
// The isCopied state should be reset to false after the timeout | ||
expect(result.current.isCopied).toBe(false); | ||
}); | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { useState } from 'react' | ||
|
||
export function useClipboard({ timeout = 2000 }: { timeout?: number }) { | ||
const [isCopied, setIsCopied] = useState<Boolean>(false) | ||
|
||
const copyToClipboard = (value: string) => { | ||
if (!value) return | ||
if (typeof window === 'undefined' || !navigator.clipboard?.writeText) return | ||
|
||
navigator.clipboard.writeText(value).then(() => { | ||
setIsCopied(true) | ||
|
||
setTimeout(() => { | ||
setIsCopied(false) | ||
}, timeout) | ||
}) | ||
} | ||
|
||
return { isCopied, copyToClipboard } | ||
} | ||
Comment on lines
+3
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we need another |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './useHash' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { useHash } from './useHash' | ||
|
||
function Component() { | ||
const hash = useHash(); | ||
|
||
return ( | ||
<div> | ||
<h1>Hash String: {hash} </h1> | ||
{/* Your component JSX */} | ||
</div> | ||
); | ||
} | ||
|
||
export default Component; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
We use the useHash hook to read the entire hash string from the URL. | ||
|
||
- [`useHashHistory`](/react-hook/use-hash-history): The useHashHistory hook is used to track changes in the URL hash over time. It returns an array containing the history of hash values, starting from the initial value when the component mounts. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { act, renderHook } from '@testing-library/react'; | ||
|
||
import { useHash } from './useHash'; | ||
|
||
|
||
describe('useHash()', () => { | ||
it('should update hash value on hashchange event', () => { | ||
const { result } = renderHook(() => useHash()); | ||
|
||
// Simulate a hashchange event with a new hash value | ||
const newHash = "newHashValue"; | ||
act(() => { | ||
window.location.hash = `#${newHash}`; | ||
const event = new Event('hashchange'); | ||
window.dispatchEvent(event); | ||
}); | ||
|
||
// Check if the hash value has been updated | ||
expect(result.current).toBe(newHash); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { useState,useEffect } from 'react'; | ||
|
||
|
||
export function useHash() { | ||
const [hash, setHash] = useState<string>(""); | ||
|
||
useEffect(() => { | ||
const handleHashChange = () => { | ||
setHash(window.location.hash.slice(1)); | ||
}; | ||
handleHashChange(); | ||
|
||
window.addEventListener("hashchange", handleHashChange); | ||
|
||
return () => { | ||
window.removeEventListener("hashchange", handleHashChange); | ||
}; | ||
}, []); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I modify the code so that it renders anew every time the value of the hash changes, will this cause problems? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Modifying the code to re-render on hash changes shouldn't inherently cause problems. It's done to ensure the component reacts to hash value changes accurately. |
||
|
||
return hash; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './useHashHistory' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
|
||
import { useHashHistory } from "./useHashHistory"; | ||
|
||
|
||
function Component() { | ||
const hashHistory = useHashHistory(); | ||
|
||
return ( | ||
<div> | ||
<h1>Hash History:</h1> | ||
<ul> | ||
{hashHistory.map((hash, index) => ( | ||
<li key={index}>{hash}</li> | ||
))} | ||
</ul> | ||
</div> | ||
); | ||
} | ||
|
||
export default Component; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
The useHashHistory hook is used to track changes in the URL hash over time. It returns an array containing the history of hash values, starting from the initial value when the component mounts. You can display this history in your component to show how the hash has changed during the user's interaction with the page. | ||
|
||
### Related hooks | ||
|
||
- [`useHash`](/react-hook/use-hash): We use the useHash hook to read the entire hash string from the URL. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { renderHook } from '@testing-library/react'; | ||
import { useHashHistory } from './useHashHistory'; | ||
|
||
describe('useHashHistory()', () => { | ||
let originalHash: string; | ||
let originalRemoveEventListener: any; | ||
|
||
beforeEach(() => { | ||
originalHash = window.location.hash; | ||
originalRemoveEventListener = window.removeEventListener; | ||
|
||
Comment on lines
+9
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you wanted to assign |
||
}); | ||
|
||
afterEach(() => { | ||
window.location.hash = originalHash; | ||
window.removeEventListener = originalRemoveEventListener; // Restore removeEventListener | ||
}); | ||
|
||
it('should initialize with the current hash', () => { | ||
const { result } = renderHook(() => useHashHistory()); | ||
|
||
expect(result.current).toEqual([originalHash]); | ||
}); | ||
|
||
it('should update history array when hash changes', () => { | ||
const { result } = renderHook(() => useHashHistory()); | ||
|
||
// Simulate a hashchange event with a new hash value | ||
const newHash = "#newHash"; | ||
window.location.hash = newHash; | ||
const event = new Event('hashchange'); | ||
window.dispatchEvent(event); | ||
Comment on lines
+28
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You might want to call it inside |
||
|
||
expect(result.current).toEqual([originalHash, newHash]); | ||
}); | ||
|
||
it('should remove event listener on unmount', () => { | ||
const { unmount } = renderHook(() => useHashHistory()); | ||
unmount(); | ||
|
||
expect(window.removeEventListener).toHaveBeenCalledWith('hashchange', expect.any(Function)); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { useState,useEffect } from 'react'; | ||
|
||
// Hook to track hash history | ||
export function useHashHistory(): string[] { | ||
const [history, setHistory] = useState<string[]>([window.location.hash]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This hook is not SSR safe, window wouldn't be defined on server. Check other examples in the repo to learn how to make it SSR safe |
||
|
||
useEffect(() => { | ||
const handleHashChange = () => { | ||
setHistory((prevHistory) => [...prevHistory, window.location.hash]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't use spread operator like that, its not a good practice. Use |
||
}; | ||
|
||
window.addEventListener("hashchange", handleHashChange); | ||
|
||
return () => { | ||
window.removeEventListener("hashchange", handleHashChange); | ||
}; | ||
}, []); | ||
|
||
return history; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './useOrigin' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { useState, useEffect } from 'react'; | ||
import { useOrigin } from './useOrigin' | ||
|
||
interface LiveClockProps {} | ||
|
||
interface Time { | ||
hours: number; | ||
minutes: number; | ||
seconds: number; | ||
} | ||
|
||
function formatTime(date: Date): Time { | ||
const hours = date.getHours(); | ||
const minutes = date.getMinutes(); | ||
const seconds = date.getSeconds(); | ||
return { hours, minutes, seconds }; | ||
} | ||
|
||
function LiveClock(props: LiveClockProps) { | ||
const origin = useOrigin(); | ||
const [currentTime, setCurrentTime] = useState<Date>(new Date()); | ||
|
||
useEffect(() => { | ||
const updateCurrentTime = () => { | ||
setCurrentTime(new Date()); | ||
}; | ||
|
||
// Update the current time every second | ||
const intervalId = setInterval(updateCurrentTime, 1000); | ||
|
||
return () => { | ||
clearInterval(intervalId); | ||
}; | ||
}, []); // Run the effect only once on component mount | ||
|
||
const { hours, minutes, seconds } = formatTime(currentTime); | ||
|
||
return ( | ||
<div className="bg-gray-100 p-8 rounded-md shadow-md"> | ||
<h2 className="text-2xl font-bold mb-4">Live Clock</h2> | ||
<p className="text-gray-600 mb-2">Current Origin: {origin}</p> | ||
<p className="text-3xl font-bold text-blue-600"> | ||
Current Time: {`${hours}:${minutes}:${seconds}`} | ||
</p> | ||
Comment on lines
+4
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does the clock has to do With |
||
</div> | ||
); | ||
} | ||
|
||
export default LiveClock; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
The useCurrentOrigin hook is a custom React hook designed to provide the origin (protocol, hostname, and port) of the current window location. This can be particularly useful in scenarios where real-time information is needed based on the user's context. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { act, renderHook } from '@testing-library/react'; | ||
import { useOrigin } from './useOrigin'; | ||
|
||
describe('useOrigin()', () => { | ||
it('should return empty string if window.location.origin is not available', () => { | ||
const originalLocation = window.location; | ||
delete (window as any).location; | ||
window.location = { origin: '' }as unknown as Location; | ||
|
||
const { result } = renderHook(() => useOrigin()); | ||
|
||
expect(result.current).toBe(''); | ||
Comment on lines
+5
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You want to test if |
||
|
||
window.location = originalLocation; | ||
}); | ||
|
||
it('should return empty string before component has mounted', () => { | ||
const { result } = renderHook(() => useOrigin()); | ||
|
||
expect(result.current).toBe(''); | ||
}); | ||
Comment on lines
+17
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as I know,
Comment on lines
+17
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as I know,
Comment on lines
+17
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as I know, |
||
|
||
it('should return window.location.origin after component has mounted', async () => { | ||
const originalLocation = window.location; | ||
delete (window as any).location; | ||
window.location = { origin: 'https://usehooks-ts.com' } as unknown as Location; | ||
|
||
let result: any; | ||
await act(async () => { | ||
result = renderHook(() => useOrigin()).result; | ||
}); | ||
|
||
expect(result.current).toBe(''); | ||
|
||
await act(async () => { | ||
await new Promise(resolve => setTimeout(resolve, 0)); // Waiting for the next microtask | ||
}); | ||
|
||
expect(result.current).toBe('https://usehooks-ts.com'); | ||
|
||
window.location = originalLocation; | ||
}); | ||
Comment on lines
+23
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This whole test doesn't make any sense. Also I don't want to point out same things about |
||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { useState ,useEffect} from 'react'; | ||
|
||
|
||
export const useOrigin = () => { | ||
const [mounted, setMounted] = useState(false); | ||
|
||
useEffect(() => { | ||
setMounted(true); | ||
}, []); | ||
|
||
const origin = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; | ||
|
||
if (!mounted) { | ||
return ""; | ||
} | ||
|
||
return origin; | ||
Comment on lines
+4
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you need a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it thanks for feedback. Will fix it soon |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This wouldn't work as
clipboard.writeText
is an async function and you are setting the value ofisCopied
in thethen
callback. So the value doesn't update immediately