-
Notifications
You must be signed in to change notification settings - Fork 160
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
Add HotKeysHOC & HOCWrappedNode for example #85
Conversation
Hey vivian-eb, thank you for your thorough pull request. I have been rather over-committed of late, but I will try and get around to looking this over in the next couple of days. |
Sounds good, thank you! 👍 |
I'm just getting back into this code base after having stepped away for a while, so some of the following may be misguided or incorrect - we'll just have to work through it together as I re-familiarise myself with the code base. I took a look at your PR and noted a few things that stood out to me while I tried to grok your changes, but I am having trouble understanding how it actually works. I am confused about the role that Thanks again for taking the time to put this together. |
lib/HotKeysHOC.js
Outdated
* @summary An HOC that provides the wrappedComponent with the ability to implement keyboard actions | ||
*/ | ||
const HotKeysHOC = (keyMap) => ((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.
I am not a particular fan of HotKeysHOC
as a name for the higher order component. I've always used the convention of camelCase names for HOC to make it easier to conceptualise them as functions. This seems to be consistent with the React docs
I also don't think it's necessary to have "HOC" in the name.
I suggest something like "withHotKeys" to remain consistent with the package and component name, and apparent community HOC naming conventions - unless you have a strong case against?
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.
the internal name we're using for Eventbrite is withKeyboardSupport
, but withHotkeys
might be more consistent with the library name. I can change it to withHotkeys
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.
Can we keep the library name capitalisation the same as well (withHotKeys
)?
lib/HotKeysHOC.js
Outdated
* without the user wrapping every component with a <HotKeys> component individually | ||
* | ||
* See examples/master/HOCWrappedNode.js for an example implementation |
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 want to add JSDoc comments to the React Hotkeys source code some time in the near future - could you please rewrite this comment to be consistent with the JSDoc standard?
I'm thinking of the @example tag in particular.
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.
added! please let me know if i can expand on anything
lib/HotKeysHOC.js
Outdated
|
||
return ( | ||
<HotKeys component="documentFragment" keyMap={keyMap} handlers={this._getBoundHandlers()}> |
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.
Perhaps I am missing something but I am not sure why you have chosen documentFragment
as the component to use, here.
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 to avoid applying unexpected stylistic changes to the wrapped component that could come from div
s.
lib/HotKeysHOC.js
Outdated
|
||
render() { | ||
let {keyboardAction, keyboardEvent} = this.state; |
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.
const
seems more appropriate than let
in this context.
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.
changed
@greena13 no problem! I wrote this HOC a few months back so I really appreciate your review. Compared to how I've added comments to _setAccessibilityState and _getBoundHandlers within the HOC file. _getBoundHandlers loops through the |
Hey, thanks for the updated code. I am not sure I'll get a chance to look at it today - I'm just headed out - but if you find the time, could you please merge master into your fork and address the merge conflicts? I think it should be as simple as deleting the |
My plans changed somewhat so I have a few minutes to look things over. So if I understand the code correctly, The child (wrapped) component must then check when it's props are changed via It strikes me that there are a number of inefficiencies with this approach. Every time a keyboard event that matches the HoC's I am also not crazy about the wrapped component having to then check to see if a new keyboard event has occurred and whether it has a matching handler. This feels like a lot of boilerplate code and and a duplication of the functionality that Perhaps we should start back at the beginning so I can get an better idea of what the benefit of a HoC is over using the HotKeys component. Is it just to provide an alternative (less declarative syntax) or is it to remove duplication? What are the advantages of using a HoC over two I believe this separation (not requiring the same I am also not opposed to supporting an alternative syntax (in the form of a HoC) if people want or prefer it - so long as it does not duplicate the existing functionality too much and essentially double the code that must be maintained and tested. |
@greena13 The original HOC was done a while back, and looking at it again, I realize that it did duplicate many functionalities. I've reworked the HOC and removed a lot of steps for the implementation. Please let me know what you think :) |
}; | ||
|
||
componentDidMount() { | ||
this.setState({handlers: this._ref.hotKeyHandlers}); |
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.
Have you tested this in the browser? I have previously had issues when I've experimented with trying to change the parameters passed to HotKeys
using state. I wish I could remember what happened and whether it was handlers
or keyMap
I was trying to change.
In any case, I came away the (potentially erroneous) understanding that HotKeys
didn't support changing its arguments after it had mounted.
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.
Yep! This was tested using the HOCWrappedNode
in the example html
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.
OK, that's good enough for me. ;)
.gitignore
Outdated
@@ -35,3 +35,7 @@ node_modules | |||
/cjs | |||
/umd | |||
/es | |||
|
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 don't think these additions are required. As far as I understand, everything is built into either cjs
, umd
or es
.
lib/withHotKeys.js
Outdated
*/ | ||
const withHotKeys = (keyMap, handlers) => ((Component) => | ||
class withHotKeysWrapper extends PureComponent { | ||
static propTypes = Component.propTypes; |
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 am not sure this is really necessary. Is there any benefit to checking the component's prop types twice - once in withHotKeysWrapper
and once in 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.
removed
lib/withHotKeys.js
Outdated
* @summary An HOC that provides the wrappedComponent with the ability to implement keyboard actions | ||
*/ | ||
const withHotKeys = (keyMap, handlers) => ((Component) => | ||
class withHotKeysWrapper extends PureComponent { |
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 the component class should be capitalised and a noun, as is consistent with JS conventions: HotKeysWrapper
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.
changed
lib/withHotKeys.js
Outdated
* @param {Object} keyMap an action-to-keyboard-key mapping | ||
* @summary An HOC that provides the wrappedComponent with the ability to implement keyboard actions | ||
*/ | ||
const withHotKeys = (keyMap, handlers) => ((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.
handlers
here doesn't seem to actually be used?
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.
removed
}; | ||
|
||
componentDidMount() { | ||
this.setState({handlers: this._ref.hotKeyHandlers}); |
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 much better than the first version. Thanks for taking the time to rework it.
I still have mixed feelings about this - reaching into the children of withHotKeysWrapper
to pick off the hotKeyHandlers
attribute feels a little like information flow in the wrong direction. I guess I am still having trouble understanding a use case for this.
From what I can see, this HoC can be used to create a component that you can pass arbitrary (but necessarily singular) children to, who define their handlers later on (but they can't change after mounting without causing withHotKeysWrapper
to update, somehow). So the actions stay the same - they are defined and encapsulated in the withHotKeysWrapper
component, but you can re-use that component and mount it with different children, who define different handlers.
So if you were only to use that withHotKeysWrapper
component once, I would say that it would be more declarative (and avoid a double render) to just use one HotKeys
component at the top of the application that defines the keyMap
and many different HotKeys
descendants who define (different) handlers
.
However, if you want to use it in many places in your application to enforce a sort of interface (children of the withHotKeysWrapper
component can/should define handlers to the sequences in the withHotKeysWrapper
'skeyMap
), then I can perhaps begin to understand there being a use case for this. But I am not sure if such a use case can be better handled slightly differently.
In my head I am thinking of it as this:
class MyComponent extends Component {
render() {
return (
<HotKeys handlers={ myHandlers } >
//...
</HotKeys>
)
}
}
class MyContainer extends Component {
render() {
return {
<HotKeys keyMap={myKeyMap} >
//-----
// ...|- <MyComponent />
// ......|- <MyComponent />
// .....|- <MyComponent />
</HotKeys>
}
}
}
Being replaced by this:
class MyComponent extends Component {
constructor(props, context) {
super(props, context)
this.hotKeyHandlers = myHandlers
}
render() {
return (
//...
)
}
}
const MyContainer = withHotKeys(myKeyMap)(MyComponent)
which does remove the reference to HotKeys
in MyComponent
, but it comes at the cost of - I think- double rendering as the withHotKeysWrapper
component must wait to have its child to get the handlers, to re-render that child with the information that it already has.
What do you think?
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 understand the concerns of 1) getting the child's state via ref and 2) one extra render on componentDidMount
. I think that these are trade-offs for 1) abstracting out <HotKeys>
from the Component
render and 2) sectioning handlers into hotKeyHandlers
instead of defining in render
(which could already be fill with other logic). Modularizing hotkeys support is advantageous for managing multiple components with different needs for keyboard support. With that said, if there's a way for the HOC to not re-render but also not duplicate HotKeys
functionalities, please let me know! You have more expertise :)
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 can't think of a way to provide a HOC that matches the functionality of the component (allowing handlers
to be defined at mount-time), so this is the best solution available to us at the moment.
Are you able to address this warning in the test suite:
And investigate the failing test? Are you also able to add TypeScript definitions for the new function to |
|
Thanks for your multiple revisions - I appreciate the time you've put into this. I can merge and add the type definition if you want, or if you want to learn or do them yourself, we just need to declare a function ( You also need to make sure that that function is declared as an export of the package. Depending on how you learn and how much time you want to spend on this, a cheatsheet like this one will be helpful, or you can start from the top. In either case take a look at what's already there and expand upon it. What you end up with should only be an additional line at the bottom of the file. |
I gave it a stab -- hopefully it's correct! |
Thanks @vivian-eb, that was indeed correct (I just refactored to re-use a definition already there). I've released that as v1.1.0 |
Resolve #63
This diff adds the HotKeysHOC to /lib and updates the example
index.html
with the newly created<HOCWrappedNode />
to show a sample implementation of the HOC. Unit tests are included.