Async data retrieval via input box is one step behind

- 1 answer

Ad

I am making a simple Wikipedia results viewer depending on the user's input.

The results list should update anytime the input box changes.

I am using $.getJSON() to make the call to the Wikipedia API. However, my results list doesn't update whenever the input box is changed. In fact, it seems like it's one step behind, so there must be something I don't comprehend with regards to updating the state, specifically from within an async call.

Having said that, the response does change as expected, as I can see in the console, so something is happening in the rendering of the results list that I don't comprehend.

From the React Debug Tools and the console, I can see that my array is updating as expected, as such:

enter image description here

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Wiki Searcher</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react-dom.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-alpha1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
</head>
<body>
    <div id="container"></div>
    <script src="script.js"></script>
</body>
</html>

Javascript

var SearchBox = React.createClass({

    changeText: function () {
        this.props.changeInputText(ReactDOM.findDOMNode(this.refs.searchBox).value);
    },

    render: function () {
        return (
            <input type="text" onChange={this.changeText} ref="searchBox"/>
        )
    }
});

var SearchItemList = React.createClass({

    render: function () {

        console.log('data:', this.props.results);
        console.log('inputtext', this.props.inputText);

        var listItems = this.props.results
            .filter(function (e, i) {
                return e.title.indexOf(this.props.inputText) !== -1;
            }.bind(this))
            .map(function (e, i) {
            return (
                <li key={i}>
                    <a target="_blank" rel="nofollow noreferrer" target="_blank" rel="nofollow noreferrer" href="#"><h3>{e.title}</h3></a>
                    <p>{e.snippet}</p>
                </li>
            )
        });
        return (
            <ul>
                {listItems}
            </ul>
        )
    }
});
//TODO: extract list item into its own component?
var Main = React.createClass({

    getInitialState: function () {
        return {
            inputText: 'javascript',
            results: [],
        }
    },

    getWikiData: function () {

        var prependage = 'https://en.wikipedia.org/w/api.php?action=query&list=search&format=json&srsearch='
        var appendage = "&callback=?";
        var url = prependage + this.state.inputText + appendage;

        $.getJSON(url, function (data) {
            this.setState(
                {results: data.query.search}
            );
        }.bind(this));
    },

    componentDidMount: function () {
        this.getWikiData();
    },

    changeInputText: function (searchTerm) {
        this.setState({
            inputText: searchTerm,
        });
        this.getWikiData();
    },

    render: function () {
        return (
            <div className="container">
                <div className="col-sm-8 col-sm-offset-2">
                    <h1>Wiki Searcher</h1>
                    <SearchBox inputText={this.state.inputText} changeInputText={this.changeInputText}/>
                    <SearchItemList results={this.state.results} inputText={this.state.inputText}/>
                </div>
            </div>
        )
    }
});

ReactDOM.render(<Main/>, document.getElementById('container'));

Plunkr

Ad

Answer

Ad

The primary error is in your assumption that calling setState, in your changeInputText function, will have changed inputText before you call getWikiData.

setState is an async function and so the new value for inputText may not be set when your getWikiData fires.

So to fix, call getWikiData in the callback which will fire when the new state is updated.

changeInputText: function (searchTerm) {
    this.setState({
        inputText: searchTerm,
    }, () => {
       this.getWikiData();
    });  
},
Ad
source: stackoverflow.com
Ad