Datsik
Datsik

Reputation: 14824

React Render not updating on response from Ajax query

I have my component that populates an array that determines what will be displayed in the render function (obviously the componentDidMount function)

But nothing happens when it updates the this.state.containers.. I've put a console.log into the first render loop, which successfully displays:

Image of JSON Response

But then nothing changes on the client side so I don't know if my loops are bad but I get no JS errors. So I'm not sure what the problem is, pretty new to React so any help would be great thanks.

var AdminForumContainer = React.createClass({
    getInitialState: function() {
        return { containers: [] }
    },
    componentDidMount: function() {
        $.ajax({
            method: 'GET',
            url: '/admin/manage/forum/populate',
            success: function(data) {
                var {containers} = this.state;
                for (var i = 0; i < data.length; i++) {
                    containers.push(data[i]);
                }
                this.setState({containers});
            }.bind(this)
        })  
    },
    createMainThread: function(containerid) {

    },
    createSubThread: function(containerid) {

    },
    render: function() {
        return (
            <div>
                {this.state.containers.map(function(container) {
                    {console.log(container)}
                    <table key={container.containerid} className="containers">
                        <caption>{container.containername}</caption>
                        <thead className="containerTitle">
                            <tr>
                                <td colspan="2">Main Threads</td>
                            </tr>
                        </thead>
                        <thead>
                            <tr>
                                <td>Thread Name</td>
                                <td>Delete</td>
                            </tr>
                        </thead>
                        <tbody>
                            {
                                container.mainthreads.map(function(mainthread) {
                                    return (
                                    <tr key={mainthread.threadid}>
                                        <td>{mainthread.threadname}</td>
                                        <td><button className="button alert">Delete</button></td>
                                    </tr>
                                    )
                                })
                            }
                            <tr>
                                <td><input type="text"/></td>
                                <td><button className="button">Create</button></td>
                            </tr>
                        </tbody>
                        <thead>
                            <tr>
                                <td colspan="2">Sub Threads</td>
                            </tr>
                        </thead>
                        <tbody>
                            {
                                container.mainthreads.map(function(subthread) {
                                    return (<tr key={subthread.threadid}>
                                        <td>{subthread.threadname}</td>
                                        <td><button className="button alert">Delete</button></td>
                                    </tr>)
                                })
                            }
                            <tr>
                                <td><input type="text"/></td>
                                <td><button className="button">Create</button></td>
                            </tr>
                        </tbody>
                    </table>
                })}
            </div>
        )
    }
});

ReactDOM.render(<AdminForumContainer/>, document.getElementById("AdminForumContainer"))

Upvotes: 0

Views: 224

Answers (2)

David L. Walsh
David L. Walsh

Reputation: 24817

You're actually mutating your state, which is best avoided. That is, your for-loop is directly updating this.state.containers, whereas state properties should be treated as read only, and only setState() should update them.

So what's happening is you've already updated this.state.containers by the time you call this.setState(). I'm no expert on React internals, but what I think is happening is setState() does a check to see if the state object property differs from the one you pass in; and here's the thing: it doesn't. The containers array you passed in is the same as this.state.containers. Therefore your component doesn't re-render.

My suggestion is to avoid mutating this.state.containers. You can use Array#concat to append the new data. Note that Array#concat doesn't mutate the array, it creates a new one.

 success: function(data) {
     // add a loop here if you need to manipulate the data response in some way
     this.setState({
         containers: this.state.containers.concat(data)
     });
 }.bind(this)

UPDATE - 10 Dec 2015

A correction is in order.

Whilst I still think the above is good advice, I no longer believe it to be the source of your problem. Experimenting with React has shown my above conjecture about the nature of setState to be false. React does not compare the old state to the new state before re-rendering (unless componentWillUpdate is overridden specifically to do this). You can call this.setState() with all or a subset of the existing state, an empty object or no argument at all, and it will still force a re-render. What it compares is not the old state to the new state, but the old rendered output to the new rendered output.

Upvotes: 0

Fran&#231;ois Richard
Fran&#231;ois Richard

Reputation: 7045

I would write it like that:

            var containers = this.state.containers;
            for (var i = 0; i < data.length; i++) {
                containers.push(data[i]);
            }
            this.setState({containers: containers});

Does it work now ? What does show this.setState({containers: containers}, function() { console.log(this.state)); ?

Upvotes: 1

Related Questions