question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

`selected` state doesn't always update correctly in v4.0.0-alpha.x

See original GitHub issue

Version

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:closed
  • Created 4 years ago
  • Reactions:1
  • Comments:10 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
ericgiocommented, Feb 26, 2020

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.

1reaction
ericgiocommented, Dec 12, 2019

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 to componentDidMount, 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:

class MyTypeahead extends React.Component {
  state = {
    selected: [],
  };

  render() {
    return (
      <Typeahead
        ...
        onChange={selected => this.setState({ selected })}
        options={[ ... ]}
        selected={this.state.selected}
      />
    );
  }
};

Given the above, let’s say a user makes a selection (eg: “California”). The (partial) typeahead state should now be:

{
  selected: ['California'],
  text: 'California',
}

If the user hits backspace, the state should update to be:

{
  selected: [],
  text: 'Californi',
}

In that case, onChange would be triggered (since the selections changed), causing MyComponent’s state to update, which would then be passed back into Typeahead as props. The problem is then how to distinguish between an updated selected 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 with componentDidUpdate there are successive updates that occur in practice that don’t seem to occur with Enzyme (hence the use of wrapper.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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

React setState not updating state - Stack Overflow
setState() is usually asynchronous, which means that at the time you console.log the state, it's not updated yet. Try putting the log in...
Read more >
Cannot Test Asynctypeahead From React-Bootstrap ...
onChange should be called before the internal state is updated whether the selected state doesn't always update correctly in v4.0.0alpha.x .
Read more >
Cant install due to conflict bin `nx` with NX CLI (nrwl) - Antfu/Ni
`selected` state doesn't always update correctly in v4.0.0-alpha.x, 10, 2019-04-13, 2022-10-14. Create Student Resources for the Course Lab ...
Read more >
gi
In React, we use setState() to update the state of any component. ... ericgio state doesn't always update correctly in v4.0.0-alpha.x ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found