-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
compose: Add types to useRefEffect and clipboard hooks #31603
Changes from all commits
0b4e9c8
2d61794
508b1af
b2ce994
32e3c94
1a4f6ef
6f3c3ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,32 +9,40 @@ import Clipboard from 'clipboard'; | |
import { useRef, useEffect, useState } from '@wordpress/element'; | ||
import deprecated from '@wordpress/deprecated'; | ||
|
||
/* eslint-disable jsdoc/no-undefined-types */ | ||
/** | ||
* Copies the text to the clipboard when the element is clicked. | ||
* | ||
* @deprecated | ||
* | ||
* @param {Object} ref Reference with the element. | ||
* @param {string|Function} text The text to copy. | ||
* @param {number} timeout Optional timeout to reset the returned | ||
* @param {import('react').RefObject<string | Element | NodeListOf<Element>>} ref Reference with the element. | ||
* @param {string|Function} text The text to copy. | ||
* @param {number} [timeout] Optional timeout to reset the returned | ||
* state. 4 seconds by default. | ||
* | ||
* @return {boolean} Whether or not the text has been copied. Resets after the | ||
* timeout. | ||
*/ | ||
export default function useCopyOnClick( ref, text, timeout = 4000 ) { | ||
/* eslint-enable jsdoc/no-undefined-types */ | ||
deprecated( 'wp.compose.useCopyOnClick', { | ||
since: '10.3', | ||
plugin: 'Gutenberg', | ||
alternative: 'wp.compose.useCopyToClipboard', | ||
} ); | ||
|
||
/** @type {import('react').MutableRefObject<Clipboard | undefined>} */ | ||
const clipboard = useRef(); | ||
const [ hasCopied, setHasCopied ] = useState( false ); | ||
|
||
useEffect( () => { | ||
/** @type {number | undefined} */ | ||
let timeoutId; | ||
|
||
if ( ! ref.current ) { | ||
return; | ||
} | ||
|
||
// Clipboard listens to click events. | ||
clipboard.current = new Clipboard( ref.current, { | ||
text: () => ( typeof text === 'function' ? text() : text ), | ||
|
@@ -48,7 +56,7 @@ export default function useCopyOnClick( ref, text, timeout = 4000 ) { | |
|
||
// Handle ClipboardJS focus bug, see https://github.com/zenorocha/clipboard.js/issues/680 | ||
if ( trigger ) { | ||
trigger.focus(); | ||
/** @type {HTMLElement} */ ( trigger ).focus(); | ||
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 know this has been merged already, but just curious: why did you wrap 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. It's how you do a type cast of a variable in JSDoc flavored TypeScript: microsoft/TypeScript#5158 |
||
} | ||
|
||
if ( timeout ) { | ||
|
@@ -59,7 +67,9 @@ export default function useCopyOnClick( ref, text, timeout = 4000 ) { | |
} ); | ||
|
||
return () => { | ||
clipboard.current.destroy(); | ||
if ( clipboard.current ) { | ||
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. Could we pull 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. Sorry I'm not sure what you are suggesting 😞 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. It seems 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. The good thing would be that you wouldn't have to check if it exists 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. Ahhh gotcha. Let me tinker around with it and see if I can get it to work. 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. Well, given it's actually deprecated I feel weird trying to refactor it 😅 Do you think it's still worth it? 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 have a strong opinion on this, it's just a generally suggestion. It's totally fine to leave it :) |
||
clipboard.current.destroy(); | ||
} | ||
clearTimeout( timeoutId ); | ||
}; | ||
}, [ text, timeout, setHasCopied ] ); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,9 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
// eslint-disable-next-line no-restricted-imports | ||
import type { DependencyList, RefCallback } from 'react'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
|
@@ -17,14 +23,17 @@ import { useCallback, useRef } from '@wordpress/element'; | |
* to be removed. It *is* necessary if you add dependencies because the ref | ||
* callback will be called multiple times for the same node. | ||
* | ||
* @param {Function} callback Callback with ref as argument. | ||
* @param {Array} dependencies Dependencies of the callback. | ||
* @param callback Callback with ref as argument. | ||
* @param dependencies Dependencies of the callback. | ||
* | ||
* @return {Function} Ref callback. | ||
* @return Ref callback. | ||
*/ | ||
export default function useRefEffect( callback, dependencies ) { | ||
const cleanup = useRef(); | ||
return useCallback( ( node ) => { | ||
export default function useRefEffect< TElement = Node >( | ||
callback: ( node: TElement ) => ( () => void ) | undefined, | ||
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. @sarayourfriend: can we use callback: ( node: TElement ) => EffectCallback, 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 believe so because that would imply that the callback's return is also able to return a desctructor function which will go one level too deep I think? Here's the definition for type EffectCallback = () => (void | (() => void | undefined)); If we used If callback: EffectCallback<TElement> But effects take no input for their callback so that will never be the case. I could be confused here though, let me know if you're seeing something I'm not. 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. Yeah, you're totally right, I was the one confused. I misread some type definitions in a different module and thought we were missing an opportunity to use a predefined type. Thanks for humouring me. :) |
||
dependencies: DependencyList | ||
): RefCallback< TElement | null > { | ||
const cleanup = useRef< ( () => void ) | undefined >(); | ||
return useCallback( ( node: TElement | null ) => { | ||
if ( node ) { | ||
cleanup.current = callback( node ); | ||
} else if ( cleanup.current ) { | ||
|
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.
Shouldn't we import from
@wordpress/element
?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.
@wordpress/element
doesn't export all the types, just the functions. We import the types directly fromreact
everywhere else as well so it's just following the already established pattern.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.
I also prefer importing the types in a comment above to leave more room for parameter descriptions
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.
Ah, I think there's lots of places where we import from
@wordpress/element
. Why don't we export the patterns there?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.
Frankly there are a lot of types to export if we were to export them from
@wordpress/element
. This would increase the maintenance cost of that already quite non-standard package. We've also already accepted importing them directly fromreact
in this case, so it would be a departure from what's already been going on.I think @sirreal had some opinions about this that he might want to chime in with.
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.
Doing this pollutes the exported types of a module, which is why I avoid it.
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.
Re-exporting React types from
@wordpress/element
was discussed here: #21767From memory: