-
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 the popover component using React Hooks #15053
Conversation
cc @nerrad in case you have an idea why the unit tests are failing :) |
|
||
afterEach( () => { | ||
jest.restoreAllMocks(); | ||
class PopoverWrapper extends Component { |
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.
TestUtils don't work with functional components. I think it's the same reason the dropdown asserters were failing.
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.
How about using this new util for hooks? I remembered they had introduced something:
https://reactjs.org/docs/hooks-faq.html#how-to-test-components-that-use-hooks
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.
Which util? We're using this one already
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 @gziolo is referring to https://reactjs.org/docs/test-utils.html#act
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.
Right! I'm using it here https://github.com/WordPress/gutenberg/pull/15053/files#diff-829fd588eade3365fbb33ef908ef9fe8R41
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.
Sidenote: Via the docs for the above, there is mention of this library. Looks kind of interesting.
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.
Sidenote: Via the docs for the above, there is mention of this library. Looks kind of interesting.
We still have over 80 tests using Enzyme 🤷♂️We can investigate, but all those abstractions need to make our life easier and ensure we don't run in the same situation like with Enzyme when they are always behind with upgrading when a new version of React comes out.
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.
Ya I agree. I only referenced the other library because it supports the latest in React and on the surface it looks like they're doing a much better job than enzyme of keeping up to date. I'll probably experiment with the library in my own projects for a bit.
For reference, the commits: I was looking to rebase locally, and it seemed like you'd already accounted for the Looking at the branch in its current form, I think we might need to be considering somewhere hereabounds to cancel and schedule the interval in response to a changing availability of gutenberg/packages/components/src/popover/index.js Lines 99 to 110 in a8ffaa3
a9a3907 and e246157 are a bit more trivial. I think you've accounted for the former already, by the presence of |
a8ffaa3
to
a5446a1
Compare
I think I updated this properly now based on your suggestions @aduth |
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.
For what it's worth, these hooks refactors are near impossible to review with the confidence because the changes are so dramatic, it may as well be a total rewrite. It's also not clear to me what benefits we're hoping to gain here, outside working out how hooks can serve us in developing components. In its current form, we lose a ton of useful context from named class functions and inline documentation, substituted instead with a scattered set of sparsely-documented effect callbacks. I'm not sure if the idea is that these effects could be individually pulled out into their own functions, but since that's not proposed here (and we're setting the precedent that it's not expected), it's hard to view this as an improvement.
Would it be fair to promote a pattern, where if previously:
class MyComponent extends Component {
constructor() {
// setup foo
}
componentDidUpdate() {
// adapt foo
}
dealWithFoo() {}
render() {}
}
It would roughly translate to a hooks-equivalent:
/**
* Useful documentation explaining the purpose of this custom hook on the
* lifecycle of a component in which it's used.
*/
function useFoo() {}
function MyComponent() {
useFoo();
// ...
}
|
||
if ( | ||
popoverPosition.yAxis !== newPopoverPosition.yAxis || | ||
popoverPosition.xAxis !== newPopoverPosition.xAxis || |
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 need to be indented more than the previous line.
I think the main gain for me here is that the flow of the computation is made clear (and the dependencies between the different variables) which helps with the maintenability.
If you take a look at the previous implementation it's not as easy as |
I think the pattern you're proposing is a good one, the downside is that we'll end up with these hooks with several arguments and hooks that don't make sense outside the component itself which asks the question of whether we need to extract them or not. @aduth I'll try that pattern here to see if I can make it work. |
I've recently started to work with hooks more in projects I'm working on and these are generally the constraints on whether I extract something to a custom hook:
An example of a custom hook I did recently fitting into that pattern was a const useDebounce = ( value, delay ) => {
const [ debouncedValue, setDebouncedValue ] = useState( value );
useEffect( () => {
const timer = setTimeout( () => {
setDebouncedValue( value );
}, delay );
return () => clearTimeout( timer );
}, [ value, delay ] );
return debouncedValue;
}; |
To be clear, I don't see the primary advantage of extraction in these cases to be for reusability (though it's certainly an advantage of the pattern applied broadly), but more about documentation and readability, both in the self-documenting nature of a function name, and the actual JSDoc describing the hook function. Especially in these larger components, the refactors end up looking (to me) like a messy continuous stream of code, with no discernible boundaries or relationships. The documentation of the previous implementation wasn't great, but in the current way we're proposing to refactor these components, it seems both worsened and also not clearly set up for how we might think they should be documented and structured. |
I extracted quickly those behaviors into "internal hooks". See this commit 59ba614 I'm starting to like that pattern, I think it brings clarity. Thoughts? Should I merge that here? |
At a glance, it looks like an improvement to me. Just the added naming ( |
@aduth I agree, It does feel way better especially with the JSDocs (I added them). |
8f8561a
to
3e8897b
Compare
Good points. And I like the extraction you did @youknowriad too 👍 |
The Popover component is also one of our most complex components and refactoring to hooks bring clarity to this component and increases maintainability. I think there are still some improvements we could make but in this version, I tried keeping the logic very similar to what we have currently in the class component.