`selected` state doesn't always update correctly in v4.0.0-alpha.x
See original GitHub issueVersion
Issue encountered on versions v4.0.0-alpha.9 and v4.0.0-alpha.8, same code works fine on v3.4.2.
Steps to reproduce
The below component (which filters out custom options before setting the selected
value), encounters the issue on the v4 alpha versions - options are displayed regardless of whether they are custom options or otherwise.
import { useState } from 'react';
import { Typeahead } from 'react-bootstrap-typeahead';
const PersonInput = function() {
const options = [{id: 1, name: 'russell'}, {id: 2, name: 'naadir'}];
const [ selected, setSelected ] = useState([options[0]]);
const onChange = function(results) {
// filter out all custom inputs
setSelected(results.filter(e => !e.customOption));
}
return (
<Typeahead
id="personName"
labelKey="name"
allowNew={() => true}
multiple
options={options}
minLength={2}
onChange={onChange}
selected={selected}
>
</Typeahead>
);
};
Expected Behavior
When selecting an existing option, it should be added as a token to the input; when creating a new, custom option, it should not be added as a token to the input. (The eventual aim of the component I’m building is that an API request would be made to create the token on the backend, and only after this has succeeded would it be added as a token to the input).
Actual Behavior
Both existing options picked from the list and custom options are added as tokens to the input. As a result, the tokens in the input do not match the value of selected
which is being passed to the Typeahead
as a prop.
Notes
Worth noting that the initial value passed to selected
seems to be being used. It’s as if selected
is being treated as if it were actually defaultSelected
.
I figured I was just doing something wrong until I tried downgrading to 3.4.2 and the same code worked exactly as I expected it to.
I’m pretty new to the javascript ecosystem - should I be using the alpha versions as a matter of course or should i be using 3.4.2? yarn add
defaulted to installing v4.0.0-alpha.9, which is what i’ve been using till I encountered this issue - and I was surprised to see an alpha version chosen by default.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:1
- Comments:10 (6 by maintainers)
Top GitHub Comments
This issue is fixed in v4.0.0-rc.1, but only because I essentially reverted the original change and went back to using
componentWillReceiveProps
😦I’ll need to spend more time figuring out a long-term solution that doesn’t use deprecated lifecycles, but in the meantime it’s holding up a bunch of other changes I want to get out.
Thanks for your willingness to help out, I really appreciate it! Here’s a bit of context:
Relevant Commit: https://github.com/ericgio/react-bootstrap-typeahead/commit/ab10829217dd39415573bf305cf8de96ba9e717f This is the primary change that moved code from
componentWillReceiveProps
tocomponentDidMount
, though it’s not the only one to update the relevant code.Behavior The reason for having this code in the first place is to help track and control the state of the typeahead’s selections and input value under different circumstances, and to make sure they update correctly based on different user actions. More specifically, when a user hits backspace to delete part of the input value and there is a selection, the selection should be cleared and the input value should update with the new string.
This is relatively straightforward in an uncontrolled component, since the typeahead isn’t receiving new
selected
values when those change, so state can simply be tracked internally without any issues. It becomes complicated when the typeahead is controlled and receiving new selections via props. Here’s the scenario I’m talking about, which I imagine to be pretty common:Given the above, let’s say a user makes a selection (eg: “California”). The (partial) typeahead state should now be:
If the user hits backspace, the state should update to be:
In that case,
onChange
would be triggered (since the selections changed), causingMyComponent
’s state to update, which would then be passed back intoTypeahead
as props. The problem is then how to distinguish between an updatedselected
prop triggered by the component vs. some other external action. This behavior actually works correctly as of now, but seems to be causing bugs in other scenarios, as the original issue describes and as you seem to have discovered.Testing As mentioned, testing needs to cover both the controlled and uncontrolled cases, and depends on the lifecycles being called (and the component updating) as expected. I think this was more straightforward when I was relying on
componentWillReceiveProps
, whereas withcomponentDidUpdate
there are successive updates that occur in practice that don’t seem to occur with Enzyme (hence the use ofwrapper.update()
). The current solution also relies on state changes being batched and updated asynchronously (which I believe to be a totally reasonable assumption), but as noted above Enzyme treats state changes synchronously.I know that’s a lot, so let me know if you have additional questions. I think just getting another set of eyes on the problem would be a huge help; it’s entirely possible that my current approach is misguided and there’s a much better or simpler solution that I haven’t been thinking of.