Skip to content

Conversation

@JonasBa
Copy link
Member

@JonasBa JonasBa commented Oct 28, 2025

Refactor and reduce the abstraction of useCopyToClipboard to mostly manage navigator.clipboard calls. The improvements decouple the hook call functionality by deferring more responsibility to the caller (inversion of control).

Changes:

  • Remove 1:1 coupling between Copy and react hook calls. (extra flexibility)
    Copy callback now receives the text arguments directly, meaning one instance of the copy callback can be called with multiple different values without doing useCopyToClipboard many times.
  • Explicit control flow (easier call tracing)
    The indirection of passing props to a hook that creates a closure and calls the callback creates an indirection where copy() requires you to see what arguments went into the call by investigating props. By changing this to copy(...arguments), we make the calls explicit
  • Remove onCopy and onError in favor of returning the promise from the copy callback
    This is now explicit, which improves readability and understanding of when these callbacks are executed.
  • Remove label handling
    While this may have been helpful, it has also been largely unused, and in many cases resulted in our tooltips changing text to "Copied!", as well as the toast messages being dispatched. Having multiple indicators for a single action is not a good user experience. If this functionality is required, it can be implemented by hooking into the returned promise value.

Given the simplification, I think it's actually sensible to argue that this probably does not need to be a react hook at all, and could simply just be function call that wraps around the navigator.clipboard API, but I'll leave that up to discussion and a future change

@github-actions github-actions bot added the Scope: Frontend Automatically applied to PRs that change frontend components label Oct 28, 2025
@codecov
Copy link

codecov bot commented Oct 28, 2025

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
12307 1 12306 10
View the full list of 1 ❄️ flaky test(s)
BalanceChangeAction re-enables form after error

Flake rate in main: 100.00% (Passed 0 times, Failed 4 times)

Stack Traces | 0.096s run time
Error: Unable to perform pointer interaction as the element inherits `pointer-events: none`:

DIV  <-- This element declared `pointer-events: none`
 DIV
  DIV
   SECTION
    FORM
     DIV
      BUTTON(label=Submit)  <-- Asserted pointer events here
    at Object.assertPointerEvents (.../sentry/node_modules/.pnpm/@testing-library+user-event@14.6.1_@testing-library+dom@10.4.0/node_modules/@.../utils/pointer/cssPointerEvents.js:47:15)
    at Object.enter (.../sentry/node_modules/.pnpm/@testing-library+user-event@14.6.1_@testing-library+dom@10.4.0/node_modules/@.../system/pointer/pointer.js:52:34)
    at PointerHost.move (.../sentry/node_modules/.pnpm/@testing-library+user-event@14.6.1_@testing-library+dom@10.4.0/node_modules/@.../system/pointer/index.js:53:85)
    at pointerAction (.../sentry/node_modules/.pnpm/@testing-library+user-event@14.6.1_@testing-library+dom@10.4.0/node_modules/@.../cjs/pointer/index.js:59:39)
    at Object.pointer (.../sentry/node_modules/.pnpm/@testing-library+user-event@14.6.1_@testing-library+dom@10.4.0/node_modules/@.../cjs/pointer/index.js:27:15)
    at Object.asyncWrapper (.../sentry/node_modules/.pnpm/@testing-library+react@16.2.0_@testing-library+dom@10.4.0_@types+react-dom@19.2.0_@type_011f94990cdc27509fa142ae9e3c3bf5/node_modules/@.../react/dist/pure.js:88:22)
    at Object.<anonymous> (.../gsAdmin/components/changeBalanceAction.spec.tsx:186:5)

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

children?: never;
onCopy?: undefined | ((copiedText: string) => void);
onError?: undefined | ((error: Error) => void);
} & Overwrite<
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type signature here was dropping the aria-label requirement from types.

@JonasBa JonasBa marked this pull request as ready for review October 28, 2025 22:02
@JonasBa JonasBa requested review from a team as code owners October 28, 2025 22:02
Copy link
Member

@scttcper scttcper left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be a mutation 🤷 either way does look better

@JonasBa
Copy link
Member Author

JonasBa commented Oct 29, 2025

could be a mutation 🤷 either way does look better

Yeah, we can just drop the hook and expose the copyToClipboard directly :D not like there's anything react related in there now anyways...

* @default {successMessage: t('Copied to clipboard'), errorMessage: t('Error copying to clipboard')}
* Pass `null` to disable any toast messages.
*/
options?: {errorMessage?: React.ReactNode; successMessage?: React.ReactNode} | null
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

really small thing but why would we need options: null if it’s already optional?

Suggested change
options?: {errorMessage?: React.ReactNode; successMessage?: React.ReactNode} | null
options?: {errorMessage?: React.ReactNode; successMessage?: React.ReactNode}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TkDodo there used to be a hook prop called hideMessages which is now expressed as passing the null value. I find null to be more explicit in this case as we initialize default success and error messages. I'd be open to better alternatives though, I'm not a big fan of it either

@JonasBa JonasBa merged commit 0fc57e0 into master Oct 30, 2025
47 checks passed
@JonasBa JonasBa deleted the jb/education/refactor branch October 30, 2025 22:04
@sentry
Copy link

sentry bot commented Oct 31, 2025

Issues attributed to commits in this pull request

This pull request was merged and Sentry observed the following issues:

isabellaenriquez pushed a commit that referenced this pull request Oct 31, 2025
Refactor and reduce the abstraction of useCopyToClipboard to mostly
manage navigator.clipboard calls. The improvements decouple the hook
call functionality by deferring more responsibility to the caller
(inversion of control).

**Changes:**
- Remove 1:1 coupling between Copy and react hook calls. (extra
flexibility)
Copy callback now receives the text arguments directly, meaning one
instance of the copy callback can be called with multiple different
values without doing `useCopyToClipboard` many times.
- Explicit control flow (easier call tracing)
The indirection of passing props to a hook that creates a closure and
calls the callback creates an indirection where `copy()` requires you to
see what arguments went into the call by investigating props. By
changing this to `copy(...arguments)`, we make the calls explicit
- Remove onCopy and onError in favor of returning the promise from the
copy callback
This is now explicit, which improves readability and understanding of
when these callbacks are executed.
- Remove label handling
While this may have been helpful, it has also been largely unused, and
in many cases resulted in our tooltips changing text to "Copied!", as
well as the toast messages being dispatched. Having multiple indicators
for a single action is not a good user experience. If this functionality
is required, it can be implemented by hooking into the returned promise
value.
   
Given the simplification, I think it's actually sensible to argue that
this probably does not need to be a react hook at all, and could simply
just be function call that wraps around the navigator.clipboard API, but
I'll leave that up to discussion and a future change
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants