-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Refactor: ColorPicker to use hooks #26770
Refactor: ColorPicker to use hooks #26770
Conversation
e4e5448
to
adaa488
Compare
ownerDocument.addEventListener( 'mouseup', handleMouseUp ); | ||
} | ||
|
||
return function cleanup() { |
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.
We should only clean up if mouseDown
is true. I would suggest to return early if mouseDown
is false.
if ( ! mouseDown ) {
return;
}
ownerDocument.addEventListener( 'mousemove', handleChange );
return () => {
ownerDocument.removeEventListener( 'mousemove', handleChange );
}
In this case we're returning the function, so it's ok for it to be anonymous. The clean up function is anonymous everywhere in the codebase.
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.
So, regarding the named functions, I only do it if you'd otherwise assign it to a constant. E.g. const onChange = () => ...
.
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 agree it's much better like this. i made the changes in daddou-ma/gutenberg@2bbe328
draftHsl: colors.hsl, | ||
draftRgb: colors.rgb, | ||
}, | ||
debounce( partial( onChangeComplete, colors ), 100 ) |
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.
Tbh, I'm not sure how this works... If onChangeComplete
is a new function on every render, the debouncing should fail?
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.
At least it should fail between re-renders. Not sure how many times this component re-renders.
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 think i should use a useCallback hook. Right ?!
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.
Yes, it would avoid creating new functions on every render, which is good for debounce
and throttle
. The only problem is that it should be done by the consumer of ColorPicker
, since only the consumer knows if there are any dependencies.
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.
Maybe it's ok to leave it as it is now and only add useCallback
for internal functions over which ColorPicker
has control.
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.
a added useCallback
in 716869d6cb2724ce2f136be8fec380cd409ad825 inside the ColorPicker
.
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 is useless if parent components return new functions on every render though: https://github.com/WordPress/gutenberg/search?q=onChangeComplete.
Not sure what's the best way around it.
@ellatrix should we add the new |
return ( | ||
<div className={ classes }> | ||
<div className="components-color-picker__saturation"> | ||
<Saturation hsl={ hsl } hsv={ hsv } onChange={ commitValues } /> |
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.
commitValues
is a new function on every render which means the useCallback
won't work.
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.
What if i move useThrottle
from Saturation
to ColorPicker
? by putting commitValues
function inside useThrottle
and useCallback
hooks in ColorPicker
level, so the throttled callback will be passed to Saturation
.
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'm not sure if it will matter much. As long as there is a dependency on onChangeComplete
, useCallback
will just return a new function on every call too. This may be alright if the function is not re-rendering all the time. I'll see if I can test this PR tomorrow.
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.
Thank you @ellatrix, i've tested with a higher delay in useDebounce
and useThrottle
and I they are working well for me. and re-render of ColorPicker
happen on every UI interraction.
Sorry for the delay in getting back to you. Could you rebase the PR? |
716869d
to
59cdc23
Compare
sorry too for taking long. I rebased the branch. |
59cdc23
to
98e0fdf
Compare
@ellatrix I've rebased the PR into 'trunk'. |
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 haven't gone over every single line of the changes but have left ideas for addressing the concerns previously raised.
To support my suggestion that the Hue
and Alpha
components should throttle their calls to onChange
as does Saturation
, see this video recorded from a recent build from trunk. The Hue
component gets quite chunky in its dragging updates and can even cause drifted execution after draging ends. Sure, it's out of scope of a pure refactor but is a small ask.
hue-slider-laggage.mp4
this.commitValues = this.commitValues.bind( this ); | ||
this.setDraftValues = this.setDraftValues.bind( this ); | ||
this.resetDraftValues = this.resetDraftValues.bind( this ); | ||
this.handleInputChange = this.handleInputChange.bind( this ); |
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.
These are stable across rerenders. To maintain that in the refactor I think the easiest option is to put them all in a lazily initialized ref. As an example of that, outside the component function something like this:
function createColorOperations( setColors ) {
const commitValues = ( data ) => { /* … */ };
const resetDraftValues = () => { /* … */ };
const setDraftValues = ( data ) => { /* … */ };
const handleInputChange = ( data ) => { /* … */ };
const throttledCommitValues = useThrottle( commitValues, 50 );
return {
commitValues,
resetDraftValues,
setDraftValues,
handleInputChange,
throttledCommitValues
};
}
Then use it inside the component function like so:
const colorOperations = useRef();
if ( ! colorOperations.current ) {
colorOperations.current = createColorOperations( setColors );
}
const {
commitValues,
resetDraftValues,
setDraftValues,
handleInputChange,
throttledCommitValues
} = colorOperations.current;
That will make commitValues
stable so it can then be throttled as done by the Saturation
component. On a more general note, Hue
and Alpha
should probably also be throttling their use of it and that's why I included creating the throttled version of the function above (a bit like @daddou-ma had suggested https://github.com/WordPress/gutenberg/pull/26770/files#r521300047).
From there I think we could remove the debounce of onChangeComplete
which was apparently only ever an intent. As Ella suspected https://github.com/WordPress/gutenberg/pull/26770/files#r519931211, it does not work (I've tested from trunk to verify). Beyond that, debounce doesn't seem right for this and throttle seems more appropriate.
draftRgb: newColors.rgb, | ||
} ); | ||
|
||
debouncedOnChangeComplete( newColors ); |
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.
debouncedOnChangeComplete( newColors ); |
The current implementation passes this as a callback for .setState
so it's asynchronous. To match that in the refactor this could be called via an effect hook (useUpdateEffect
from '../utils' to avoid the initial render). This would simplify creating a stable commitValues
as in my preceding comment and is the main reason I'm suggesting it.
Thanks for contributing, @daddou-ma! The |
Related to #22890
Description
Refactored ColorPicker Components to functional components.
How has this been tested?
I tested the component from the storybook,
npm run storybook:dev
Types of changes
Refactor :
ColorPicker
to functional component.Refactor : ColorPicker
Hue
to functional component.Refactor : ColorPicker
Saturation
to functional component.Refactor : ColorPicker
Alpha
to functional component.Refactor : ColorPicker
Inputs
to functional component.Update Test : ColorPicker Tests by putting test into TestRenderer.act() function.
Update Snapshot: ColorPalette Snapshot by removing Pure and withInstance HOCs.
Checklist: