Skip to content
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

On First Focus, Suggestions Don't Render if a Value is Present #126

Closed
BigPrimeNumbers opened this issue Feb 18, 2016 · 17 comments
Closed

Comments

@BigPrimeNumbers
Copy link

I'm running into an issue where an Autosuggest field is not rendering suggestions on first focus if the field is initially rendered with a value. For instance, if you change the 'Basic' Codepen example to initialize state with a value of "C", no suggestion will be rendered when the field first gains focus (when it should give the suggestions "C, C++, C#, and Clojure). Editing the field kicks the suggestions into gear, and they begin appearing after that without issue.

I'm building a form that at times might have some fields initialized with values that are partial matches for suggestions, and would like to see the suggestions show up on first focus.

Is this a bug, or am I doing something wrong? Thank you in advance!

@jbasrai
Copy link

jbasrai commented Feb 18, 2016

It's because the suggestions prop isn't populated until something triggers onSuggestionsUpdateRequested. You can initialize it by doing something like this:

componentDidMount() {
  this.setState({
    suggestions: getSuggestions(this.state.value)
  });
}

@BigPrimeNumbers
Copy link
Author

Unfortunately, adding this doesn't fix the issue. The basic flow is this:

  1. componentWillMount -> perform a search based on the current field's value for suggestions. This is performed from an Alt container higher up the React chain in my program.
  2. render -> the results of the search have not propagated back down to the Autocomplete container yet.
  3. using componentDidMount at this point to try to get the suggestions won't work because the results still have not propagated to the autocomplete component.
  4. willReceiveProps fires as the results make their way down to the AC component. Updating the state at this point totally breaks the AC container for some reason - see next point
  5. render fires -> if I updated the state in willReceiveProps, the appropriate suggestions will be available (and rendered) immediately, but AC automatically selects the second suggestion and never fires again after that; if I don't, then the suggestions won't show up until an edit is made, but works fine after that.

Here is the general idea for my code:

export default class AutosuggestComponent extends React.Component{

    constructor(props) {
        super(props)
        this.bind()

        this.state = {
            value: this.props.value || "",
            placeholder: this.props.placeholder || "",
            suggestions: this.getSuggestions()
        }
        this.possibilities = this.props.possibilities || []
        this.search = this.props.searchFunction || (() => {return []})
        this.externalOnChange = this.props.onChange || (() => {return})
    }

    getSuggestions(value = "") {
        const inputValue = value.trim().toLowerCase();
        const inputLength = inputValue.length;
        console.log("get")
        return (inputLength === 0 ? [] : this.possibilities.filter(possibility =>
            possibility.value.toLowerCase().slice(0, inputLength) === inputValue))
    }
...
 componentWillReceiveProps(nextProps) {
        console.log("will receive props")
        this.possibilities = nextProps.possibilities || []
        console.log(nextProps)
        /*const suggestions = this.getSuggestions(nextProps.value)
        this.setState({
            suggestions: suggestions
        })*/
    }

    componentWillMount() {
        console.log("will mount")
        this.search()
    }

    componentDidMount() {
        console.log("did mount")
        this.setState({
            suggestions: this.getSuggestions(this.state.value)
        })
    }

    render() {
        console.log("render")
        const {value, placeholder, suggestions} = this.state
        const inputProps = {
            value,
            placeholder,
            onFocus: this.onFocus,
            onChange: this.onChange
        }
        return (<Autosuggest id={value}
            suggestions={suggestions}
            getSuggestionValue={this.getSuggestionValue}
            shouldRenderSuggestions={this.shouldRenderSuggestions}
            renderSuggestion={this.renderSuggestion}
            inputProps={inputProps}
            onSuggestionsUpdateRequested={this.onSuggestionsUpdateRequested}
        />)
    }
}

@jbasrai
Copy link

jbasrai commented Feb 19, 2016

I edited the Basic Codepen to show the implementation in action: http://codepen.io/anon/pen/rxgjZx. My changes were:

this.state = {
  value: 'C',
  suggestions: getSuggestions('C')
};

The important thing is to make sure that the suggestions prop is being set. I would step through your logic and make sure that this.getSuggestions in your componentDidMount is returning a non-empty array.

@BigPrimeNumbers
Copy link
Author

Ah, see that still won't work because the possible suggestions aren't available at that time; the constructor is called before the componentWillMount method is called. Doing that in my code returns an empty array. so no suggestions are displayed.

If there was some way to force the AC component to show results when the component has focus, that might do it for me, but I don't know how to make that happen.

@jbasrai
Copy link

jbasrai commented Feb 19, 2016

Oh it's not necessary to have the suggestions loaded in getInitialState. You can also do it in componentDidMount like in your example. Could your problem be that you haven't overriden shouldRenderSuggestionsProp and that your initial value is blank?

If you have, then I would suggest placing a breakpoint in componentDidMount and checking that suggestions is being updated properly.

@BigPrimeNumbers
Copy link
Author

No unfortunately that's not it either - I did overwrite that one :(

So in the basic codepen example, languages is pre-populated with languages to choose from. In my example, imagine languages starts empty, but gets filled in when the search function is called on the database. It gets filled in after the constructor fires, when componentWillMount then fires (and incidentally, when onFocus fires as well, to get updates). So onSuggestionsUpdateRequested is never called until an edit occurs. Unfortunately, I can't update the suggestions in componentWillMount because that gets called every single time you change anything in the field, including keying over a suggestion.

@jbasrai
Copy link

jbasrai commented Feb 19, 2016

Could your problem be that in your render method, you are passing in a local variable { suggestions } that wouldn't be defined? this.state.suggestions instead?

@BigPrimeNumbers
Copy link
Author

No, sadly that's not it either. The deconstruction of state works as expected in render, the problem is at that point, this.state.suggestions has an empty array.

@jbasrai
Copy link

jbasrai commented Feb 19, 2016

Sorry missed that. So all that remains is to get your suggestions state populated by the async database call right? Have you taken a look at the async example? If you get the suggestions state to update properly, then you'll trigger a re-render and everything should work as expected.

@BigPrimeNumbers
Copy link
Author

I did take a look at that, and while that would be a good place to get the update for suggestions, the problem is that the possibilities are returned via a component higher up the React chain, so a re-render is called when the search is performed, and the suggestions wouldn't get properly calculated at that point.

Basically, I need to get the new suggestions and display them AFTER the AC component render is triggered from the search.

@jbasrai
Copy link

jbasrai commented Feb 19, 2016

If suggestions are being populated from a higher component, you can have them bound to state in the higher component and then pass them into the Autocomplete component via props. That way when the async call finishes, it will trigger a re-render of the Autocomplete component with the updated suggestions.

@BigPrimeNumbers
Copy link
Author

Close, but the suggestions are being calculated locally in the AC component; it's the POSSIBILITIES that are being computed in a higher component.

So this is like the language possibilities in the BASIC example, except instead of being static, they are being populated dynamically from a HUGE DB via the search within the AC component. The reason the possibilities need to be updated dynamically is because of the cost of the API call on the DB, and avoiding the search returning the entire DB. So it's not an issue of computing the suggestions when the AC components are first created, but the fact that the possibilities aren't available until the AC component is first constructed, but after the first edit they are.

@jbasrai
Copy link

jbasrai commented Feb 19, 2016

I think I'm starting to understand a bit better, thanks for bearing with me. So to clarify, your problem is that your total data set possibilities is updated when the higher component returns the result of the async call. But suggestions isn't updated until the input is changed, which fires onSuggestionsUpdateRequested. Is that correct?

I see in the above example that you've commented out code in willReceiveProps, but that looks like the right idea. Why was that not working for you?

@BigPrimeNumbers
Copy link
Author

Cool, glad we're getting on the same page :)

So yes, your summary is correct: the total data set possibilities is updated when the higher component returns the result of the async call, but suggestions isn't updated until the input is changed.

The code in willReceiveProps is commented out because if I update the suggestions as soon as I get back the possibilities, the AC component behaves undesirably. Because the suggestions get updated whenever the current value in state changes (which occurs when keying over them), the suggestion list changes immediately, which is undesirable.

For example, you have 5 suggestions: Company_A, Company_B, Company_AA, and Company_AAA. If you key over Company_A, the suggestions immediately get pared down to Company_A, Company_AA, and Company_AAA, which is undesirable if you are trying to key down to Company_B. I'm thinking I might get around this by telling state not to update on key up or down (instead performing more like mouse hovering).

@jbasrai
Copy link

jbasrai commented Feb 19, 2016

react-autosuggest already handles the behavior when cycling through suggestions with the arrow keys, so I think it's best to leave the heavy lifting to that. Instead, in willReceiveProps, could you check if possibilities changes, and then only update suggestions on that condition?

@BigPrimeNumbers
Copy link
Author

Okay, so I finally got it working the way I want to, and here's how:

    onSuggestionsUpdateRequested({ value }) {
        this.setState({
            suggestions: this.getSuggestions(value)
        })
        this.search()
    }

    onSuggestionSelected(event, { suggestion, suggestionValue, method }) {
        this.setState({
            value: suggestion.value
        })
        this.externalOnChange(event, {newValue: suggestion.value})
    }

    onChange(event, {newValue, method}) {
        if (method !== "up" && method !== "down") {
            this.setState({
                value: newValue
            })
            this.externalOnChange(event, {newValue})
        }
    }

    componentWillReceiveProps(nextProps) {
        this.possibilities = nextProps.possibilities || []
        const suggestions = this.getSuggestions(this.state.value)
        this.setState({
            suggestions: suggestions
        })
    }

    componentWillMount() {
        this.search()
    }

While I realize that AC is handling the cycling of suggestions when using the arrow keys, it had the undesirable (at least in my case) effect of updating the field's value (as I outlined in the example I gave above).

Thanks for all of your input and effort; I appreciate it! Now the suggestions are populated immediately, and the field acts as desired :)

@jbasrai
Copy link

jbasrai commented Feb 20, 2016

good work figuring it out!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants