web-dev-qa-db-fra.com

React (Facebook): état géré des cases à cocher contrôlées

J'ai un petit problème en essayant de créer une case à cocher qui sélectionne et désélectionne d'autres cases à cocher individuelles (tout sélectionner/désélectionner) avec Réagir . J'ai lu http://facebook.github.io/react/docs/forms.html et découvert qu'il existe des différences entre contrôlé et non contrôlé <input> s. Mon code de test est le suivant:

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'));

Le "sélecteur global" fonctionne comme prévu: lorsqu'il est sélectionné, tous les autres contrôles sont sélectionnés. Le problème est que le gestionnaire __changeSelection() n'est pas déclenché lorsque l'une des autres cases à cocher est activée.

Je ne sais pas quelle est la bonne façon de faire fonctionner cela. Peut-être que le modèle React n'est pas le meilleur pour modéliser ce type d'interaction? Que puis-je faire?

Merci d'avance

32
matheus.emm

Dans votre fonction render, la portée de this pour la fonction de mappage checks est différente de render, qui est la portée dont vous avez besoin pour __changeSelection, donc this.__changeSelection ne localisera pas une propriété __changeSelection. Si vous ajoutez une .bind(this) à la fin de cette fonction de mappage, vous pouvez lier sa portée à la même this que 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));

Sur une note latérale, je passerais simplement le id à la fonction de gestionnaire au lieu d'attribuer des attributs de données. Cela supprimera la nécessité de localiser cet élément dans votre gestionnaire:

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));

Mettez ensuite à jour votre fonction __changeSelection Pour passer le id comme premier argument et supprimez la ligne de recherche d'attribut:

__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 });

}

Voici un exemple de tout cela mis en place, avec un jsfiddle pour que vous puissiez l'essayer :

/** @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'));
43
Michael LaCroix

Si vous avez affaire à des cases à cocher, vous pouvez utiliser l'attribut checkedLink. Voici une autre implémentation possible, qui rend la case à cocher globale contrôlée (au lieu d'être incontrôlée dans les réponses actuelles):

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>
        );
    },

});

Il est plus simple d'utiliser checkedLink=this.linkState("checkboxValue") avec LinkedStateMixin si l'état à muter n'est pas profondément imbriqué (comme c'est le cas dans cette question)

Modifier : checkedLink et valueLink sont déconseillés mais ont été recommandés dans les versions précédentes de React.

3
Sebastien Lorber