문제

I'm having a little problem while trying to create a checkbox that selects and deselects other individual checkboxes (select/deselect all) with React. I've read http://facebook.github.io/react/docs/forms.html and discovered that there are differences between controlled and not-controlled <input>s. My test code is as follows:

var Test = React.createClass({
    getInitialState: function() {
        return {
            data: [
                { id: 1, selected: false },
                { id: 2, selected: false },
                { id: 3, selected: false },
                { id: 4, selected: false }
            ]
        };
    },
    render: function() {
        var checks = this.state.data.map(function(d) {
            return (
                <div>
                    <input type="checkbox" data-id={d.id} checked={d.selected} onChange={this.__changeSelection} />
                    {d.id}
                    <br />
                </div>
            );
        });
        return (
            <form>
                <input type="checkbox" ref="globalSelector" onChange={this.__changeAllChecks} />Global selector
                <br />
                {checks}
            </form>
        );
    },
    __changeSelection: function(e) {
        var id = e.target.getAttribute('data-id');
        var state = this.state.data.map(function(d) {
            return {
                id: d.id,
                selected: (d.id === id ? !d.selected : d.selected)
            };
        });

        this.setState({ data: state });

    },
    __changeAllChecks: function(e) {
        var value = this.refs.globalSelector.getDOMNode().checked;
        var state = this.state.data.map(function(d) {
            return { id: d.id, selected: value };
        });

        this.setState({ data: state });
    }
});

React.renderComponent(<Test />, document.getElementById('content'));

The "Global selector" works as expected: when selected, all other checks are selected. The problem is that the __changeSelection() handler is not fired when one of the other checkboxes are clicked.

I don't know what is the proper way to make this work. Maybe React model is not the best one to model this kind of interaction? What could I do?

Thanks in advance

도움이 되었습니까?

해결책

In your render function, the scope of this for the checks mapping function is different from render, which is the scope you need for __changeSelection, so this.__changeSelection won't locate a __changeSelection property. If you add a .bind(this) to the end of that mapping function, you can bind it's scope to the same this as render:

var checks = this.state.data.map(function(d) {
    return (
        <div>
            <input type="checkbox" data-id={d.id} checked={d.selected} onChange={this.__changeSelection} />
            {d.id}
            <br />
        </div>
    );
}.bind(this));

On a side note, I would just pass the id to the handler function instead of assigning data-attributes. This will remove the need to locate that element in your handler:

var checks = this.state.data.map(function(d) {
    return (
        <div>
            <input type="checkbox" checked={d.selected} onChange={this.__changeSelection.bind(this, d.id)} />
            {d.id}
            <br />
        </div>
    );
}.bind(this));

Then update your __changeSelection function to pass in the id as the first arg and remove the attribute lookup line:

__changeSelection: function(id) {
    var state = this.state.data.map(function(d) {
        return {
            id: d.id,
            selected: (d.id === id ? !d.selected : d.selected)
        };
    });

    this.setState({ data: state });

}

Here is an example of it all put together, along with a jsfiddle for you to try it out:

/** @jsx React.DOM */

var Test = React.createClass({
    getInitialState: function() {
        return {
            data: [
                { id: 1, selected: false },
                { id: 2, selected: false },
                { id: 3, selected: false },
                { id: 4, selected: false }
            ]
        };
    },
    render: function() {
        var checks = this.state.data.map(function(d) {
            return (
                <div>
                    <input type="checkbox" checked={d.selected} onChange={this.__changeSelection.bind(this, d.id)} />
                    {d.id}
                    <br />
                </div>
            );
        }.bind(this));
        return (
            <form>
                <input type="checkbox" ref="globalSelector" onChange={this.__changeAllChecks} />Global selector
                <br />
                {checks}
            </form>
        );
    },
    __changeSelection: function(id) {
        var state = this.state.data.map(function(d) {
            return {
                id: d.id,
                selected: (d.id === id ? !d.selected : d.selected)
            };
        });

        this.setState({ data: state });

    },
    __changeAllChecks: function() {
        var value = this.refs.globalSelector.getDOMNode().checked;
        var state = this.state.data.map(function(d) {
            return { id: d.id, selected: value };
        });

        this.setState({ data: state });
    }
});

React.renderComponent(<Test />, document.getElementById('content'));

다른 팁

If you are dealing with checkboxes you can use the checkedLink attribute. Here is another possible implementation, that makes the global checkbox controlled (instead of uncontrolled in the current answers):

JsFiddle

var Test = React.createClass({

    getInitialState: function() {
        return {
            globalCheckbox: false,
            data: [
                { id: 1, selected: false },
                { id: 2, selected: false },
                { id: 3, selected: false },
                { id: 4, selected: false }
            ]
        };
    },

    changeCheckForId: function(id,bool) {
        this.setState(
            {
            data: this.state.data.map(function(d) {
                var newSelected = (d.id === id ? bool : d.selected);
                return {id: d.id, selected: newSelected};
            }
        )});
    },

    changeCheckForAll: function(bool) {
        this.setState({
                globalCheckbox: true,
                data: this.state.data.map(function(d) {
                    return {id: d.id, selected: bool};
                })
        });
    },



    linkCheckbox: function(d) {
      return {
         value: d.selected,
         requestChange: function(bool) { this.changeCheckForId(d.id,bool); }.bind(this)
      };
    },

    linkGlobalCheckbox: function() {
      return {
         value: this.state.globalCheckbox,
         requestChange: function(bool) { this.changeCheckForAll(bool); }.bind(this)
      };
    },

    render: function() {
        var checks = this.state.data.map(function(d) {
            return (
                <div>
                    <input key={d.id} type="checkbox" checkedLink={this.linkCheckbox(d)} />
                    {d.id}
                    <br />
                </div>
            );
        }.bind(this));

        return (
            <form>
                <input type="checkbox" checkedLink={this.linkGlobalCheckbox()} />Global selector
                <br />
                {checks}
            </form>
        );
    },

});

It is simpler to use checkedLink=this.linkState("checkboxValue") with LinkedStateMixin if the state to mutate is not deeply nested (like this is the case in this question)

Edit: checkedLink and valueLink are being deprecated but were recommmended in previous versions of React.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top