feat(dropdown): control highlightedIndex from Dropdown#966
feat(dropdown): control highlightedIndex from Dropdown#966jurokapsiar merged 20 commits intomasterfrom
Conversation
There was a problem hiding this comment.
Sorry, but I lost there 😴 If you will try to cover this code with UTs you will not survive 🦇
- Can we extract code that calculates only
highlightedIndexbased on changes/props? - Can we dispatch DOM effects in
componentDidUpdate()? I.e. in a single place based on the component's state.
componentDidUpdate(prevProps, prevState) {
if (!prevState.open && this.state.open) {
if (search) {
this.listRef.current.focus()
}
if (multiple) {
this.triggerRef.current.focus()
}
}
}There was a problem hiding this comment.
I extracted the code, you can take a look. as for the DOM effects, it's out of scope, but we can create a separate issue. I also want to move the methods around in the Dropdown, since it's kind of chaos there. should be render, then renderWhatevers, then event handlers, then helpers. Or something like that, but grouped better, since it's a 1100 line file.
There was a problem hiding this comment.
I don't understand why we need this prop when we can control the highlightedIndex directly, can you please clarify?
There was a problem hiding this comment.
we need it so that users don't have to make a whole different highlightedIndex controlling logic when they just want for the Dropdown to always open at a specified index. For example Add People picker in WebClient, that opens always with first item highlighted.
There was a problem hiding this comment.
For me it looks like a case with controlled usage, I think that we should not introduce new props that do exactly the same thing.
/cc @kuzhelov
There was a problem hiding this comment.
I totally agree. If you look at Downshift's documentation, for controlled props, they have also two helpers, initial and default. initial works similar to our default while default works similarly to this reset. I personally think both of them are useful, along with the actual controlling prop. so if we can converge to a solution, that'd be great.
There was a problem hiding this comment.
followed up with this #970 in which we can discuss more on the subject.
Changed dependencies in
Generated by 🚫 dangerJS |
Codecov Report
@@ Coverage Diff @@
## master #966 +/- ##
==========================================
+ Coverage 81.76% 82.16% +0.39%
==========================================
Files 701 701
Lines 8573 8597 +24
Branches 1245 1251 +6
==========================================
+ Hits 7010 7064 +54
+ Misses 1548 1517 -31
- Partials 15 16 +1
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
What is a actual reason to introduce IIFE there? I also see that it's the same function as on line 1073 🤔
There was a problem hiding this comment.
will change it, I had it when this was in the onStateChange.
There was a problem hiding this comment.
This method returns undefined while getHighlightedIndexOnClose() returns null 💭
There was a problem hiding this comment.
May be in this case we should return the current value?
There was a problem hiding this comment.
it's like this because when the menu opens by arrow up/down, we want to modify the highlighted index already saved in state. otherwise, it needs to be left alone, so it will be opened at whatever it was set in the close function.
when the menu closes, we want the index to be either selectedItem for single selection, 0 in case boolean prop is passed with true, or null if nothing else, so we make sure the dropdown opens with no highlight.
There was a problem hiding this comment.
Now we have these methods that are much more readable 👍
Can we simplify more?
const highlightedIndex = changes.isOpen ? this.getHighlightedIndexOnArrowKeyOpen(changes) : this.getHighlightedIndexOnClose(changes)
const newState = { open: changes.isOpen, highlightedIndex }
if (changes.isOpen && !this.props.search) {
this.listRef.current.focus()
}There was a problem hiding this comment.
the only solution is to have const newState = { open: changes.isOpen, ...{highlightedIndex} } to make sure that the highlightedIndex is not passed as undefined. otherwise Downshift will complain that we are transforming a controlled prop to uncontrolled.
|
should also aim to fix #979 |
bmdalex
left a comment
There was a problem hiding this comment.
some comments, good job 👍
There was a problem hiding this comment.
logic on lines 651 and 652 was encapsulated in the method that used to be at line 620; can you please use that again?
this.trySetStateAndInvokeHandler('onOpenChange', null, newState)There was a problem hiding this comment.
thanks, will do
There was a problem hiding this comment.
we were using switch/case for this type of conditions; let's aim for consistency and do the same here
There was a problem hiding this comment.
this should have been if/else statement anyway as the conditions are independent
There was a problem hiding this comment.
can we encapsulate logic form lines 1072-1077 (and 1063-1068) in smaller methods? logic is complex and because of branching it becomes hard to read
There was a problem hiding this comment.
there are just 4 lines each, and a separate method with some more if-else to change values depending on that stateChangeTypes ... I would rather leave it like this.
There was a problem hiding this comment.
NIT: for readability:
private getHighlightedIndexOnClose = (): number => {
const { highlightFirstItemOnOpen, items, multiple, search } = this.props
const { value } = this.state
if (!multiple && !search && value) {
// in single selection, if there is a selected item, highlight it.
return items.indexOf(value)
}
if (highlightFirstItemOnOpen) {
// otherwise, if highlightFirstItemOnOpen prop is provied, highlight first item
return 0
}
// otherwise, highlight no item
return null
}There was a problem hiding this comment.
thanks, will use that version
1f11461 to
aadf824
Compare
Means to control
highlightedIndexprop and fixes #903