Ad

SetState Is Not Updating The State By The Time The Flux Store Action Is Called

- 1 answer

I have 2 components. a project container and a project setting panel (there will be more project setting panels later on).

I have a switch inside the panel calling "switchSubmit" function that updates the state and then calls "handleSubmit" function which contains an action that updates a Flux Store based on the value of a Switch Button, I am setting the state but it always seems to be to be the previous value selected by the time my action is called.

I have read that setState isn't synchronous and I have seen some examples but don't know how to apply the correct calls and functions to set the state and have the updated state ready to use when calling the action in this particular code I am writing.

Here is my code:

var React = require('react/addons');
var Actions = require('../../Actions');
var ConfigurationStore = require('../../stores/Configuration');

var Controller = React.createClass({
    getInitialState: function () {

        ConfigurationStore.reset();

        Actions.getConfigurationSettings();

        return this.getStateFromStores();
    },

    getStateFromStores: function () {
        return {
            configuration: ConfigurationStore.getState()
        };
    },

    componentDidMount: function () {
        ConfigurationStore.addChangeListener(this.onStoreChange);
    },

    componentWillUnmount: function () {
        ConfigurationStore.removeChangeListener(this.onStoreChange);
    },

    onStoreChange: function () {
        this.setState(this.getStateFromStores());
    },


    render: function () {

        return (
            <section className="section-settings container">
                <h1 className="page-header">Projects</h1>
                <div className="row">
                    <div className="col-sm-12">
                        <div className="row">
                            <div className="col-sm-3">
                            </div>
                            <div className="col-sm-9">
                                <h2>Project Settings</h2>
                                <hr />
                                <ProjectContainer data={this.state.configuration} />
                            </div>
                        </div>
                    </div>
                </div>
            </section>
        );

    }

});    


var ProjectContainer = React.createClass({

    getInitialState: function () {
        return {
            active: false

        };
    },

    componentWillReceiveProps: function (nextProps) {
        this.setState({
            active: nextProps.data.active
        });
    },

    render: function () {
        return (
            <div>
                <ProjectPanel data={this.props.data}></ProjectPanel>
            </div>
        );

    }
});


var ProjectPanel = React.createClass({

    getInitialState: function () {
        return {
            active: false
        };
    },

    componentWillReceiveProps: function (nextProps) {
        this.setState({
            active: nextProps.data.active
        });
    },

    handleSubmit: function () {
        Actions.updateConfigurationSettings({
            active: this.state.active
        });
    },

    switchSubmit: function(event) {
        event.stopPropagation();

        this.setState({
            active: event.currentTarget.checked
        });

        this.handleSubmit();

    },

    render: function() {

        var formElements = (
                <fieldset>
                    <SwitchButton
                        name="switch-1"
                        onChange={this.switchSubmit}
                        checked={this.props.active}
                    />
                </fieldset>
            );
        }

        return (
            <div className="project-holder">
                <div className="project-config">
                    <form onSubmit={this.handleSubmit}>
                        {formElements}
                    </form>
                </div>
            </div>
        );
    }
});

Any help would be greatly appreciated!

Ad

Answer

To answer your question directly, setState accepts a callback that can be used to defer code until after render has completed. However, you should not need to use that in your scenario.

The problem I see with your code is that you have multiple sources of truth for the 'active' state.

I have updated your code to address that issue:

var React = require('react/addons');
var Actions = require('../../Actions');
var ConfigurationStore = require('../../stores/Configuration');

var Controller = React.createClass({
    getInitialState: function () {

        ConfigurationStore.reset();

        Actions.getConfigurationSettings();

        return this.getStateFromStores();
    },

    getStateFromStores: function () {
        return {
            configuration: ConfigurationStore.getState()
        };
    },

    componentDidMount: function () {
        ConfigurationStore.addChangeListener(this.onStoreChange);
    },

    componentWillUnmount: function () {
        ConfigurationStore.removeChangeListener(this.onStoreChange);
    },

    onStoreChange: function () {
        this.setState(this.getStateFromStores());
    },


    render: function () {

        return (
            <section className="section-settings container">
                <h1 className="page-header">Projects</h1>
                <div className="row">
                    <div className="col-sm-12">
                        <div className="row">
                            <div className="col-sm-3">
                            </div>
                            <div className="col-sm-9">
                                <h2>Project Settings</h2>
                                <hr />
                                <ProjectContainer data={this.state.configuration} />
                            </div>
                        </div>
                    </div>
                </div>
            </section>
        );

    }

});    

var ProjectContainer = React.createClass({
    render: function () {
        return (
            <div>
                <ProjectPanel data={this.props.data}></ProjectPanel>
            </div>
        );

    }
});


var ProjectPanel = React.createClass({
    handleSubmit: function () {
        // Do something interesting on submit.
    },

    switchSubmit: function(event) {
        event.stopPropagation();
        Actions.updateConfigurationSettings({
            active: event.target.checked
        });
    },

    render: function() {

        var formElements = (
                <fieldset>
                    <SwitchButton
                        name="switch-1"
                        onChange={this.switchSubmit}
                        checked={this.props.data.active}
                    />
                </fieldset>
            );
        }

        return (
            <div className="project-holder">
                <div className="project-config">
                    <form onSubmit={this.handleSubmit}>
                        {formElements}
                    </form>
                </div>
            </div>
        );
    }
});

There is no need to hold local state in either the ProjectContainer or the ProjectPanel. The 'active' state flows down to these components via the props.

I didn't put any code in your handleSubmit button because I didn't feel that what you had there originally made sense. You would just need whatever code you would want to run on submit.

Ad
source: stackoverflow.com
Ad