web-dev-qa-db-fra.com

Création d'éléments <select> dans React JS

Je réécris l'interface utilisateur de mon application Web dans react.js, et je suis un peu perplexe par le problème suivant.

J'ai une page qui affiche les données obtenues via une demande AJAX, et en dessous, un formulaire pour soumettre de nouvelles données s'affiche. Tout va bien.

Maintenant, je veux ajouter un <select> élément dans le formulaire et récupérer les valeurs à partir d'un emplacement différent (url).

Le code actuel (sans le <select>) ressemble à ceci (simplifié un peu, mais tous les détails de travail sont les mêmes; il suit principalement le tutoriel sur le site react.js):

var tasks_link = $('#tasks_link');

var getDataMixin = {
        loadDataFromServer: function() {
            $.ajax({
                url: this.props.url,
                dataType: 'json',
                success: function(data) {
                    this.setState({data: data});
                }.bind(this),
                error: function(xhr, status, err) {
                    console.error(this.props.url, status, err.toString());
                }.bind(this)
            });
        },
        getInitialState: function() {
            return {data: []};
        },
        componentDidMount: function() {
            this.loadDataFromServer();
        }
    };

var sendDataMixin = {
        handleDataSubmit: function(senddata) {
            $.ajax({
                url: this.props.url,
                dataType: 'json',
                contentType: 'application/json',
                type: 'POST',
                data: senddata,
                success: function(data) {
                    var curr_d = this.state.data;
                    var curr_d_new = curr_d.concat([data]);
                    this.setState({data: curr_d_new});
                }.bind(this),
                error: function(xhr, status, err) {
                    console.error(this.props.url, status, err.toString());
                }.bind(this)
            });
        }
    };

var taskForm = React.createClass({
        handleSubmit: function() {
            var name = this.refs.task_name.getDOMNode().value.trim();
            if (!name) {
              return false;
            }
            this.props.onTaskSubmit(JSON.stringify({name: name}));
            this.refs.task_name.getDOMNode().value = '';
            return false;
        },
        render: function () {
            return (
                <form className="well base_well new_task_well" onSubmit={this.handleSubmit}>
                    <div className="form-group">
                        <div className="input-group">
                            <span className="input-group-addon no_radius">Task name</span>
                            <input type="text" className="form-control no_radius" id="add_new_project_input" ref="task_name"/>
                        </div>
                    </div>
                    <button type="button" className="btn btn-default no_radius add_button" id="add_new_task_btn" type="submit">Add task</button>
                </form>
            );
        }
    });

var taskBox = React.createClass({
        mixins: [getDataMixin, sendDataMixin],
        render: function () {
            return (
                <div id="project_box" className="taskBox"> <taskList data={this.state.data} />
                    <taskForm onTaskSubmit={this.handleDataSubmit}/> </div>
            );
        }
    });

tasks_link.click(function() {
        React.renderComponent(
            <taskBox url="/api/tasks/" />,
            document.getElementById('content_container')
        );
    });

Maintenant, je peux ajouter un élément select en ajoutant un getDataMixin à TaskForm, en récupérant les données et en construisant une liste d'options possibles, mais je devrai avoir des formulaires avec plusieurs listes, et l'approche ne semble pas évoluer (en raison de collisions de noms; ou je devrai utiliser autre chose que des mixins).

J'ai donc pensé à créer un React class, qui aurait simplement le getDataMixin, recevra l'URL de l'API via le parent en définissant son props, et rendra le <select> élément; et utilisez cette classe dans le formulaire. Mais je ne sais pas comment accéder à la valeur sélectionnée (puisqu'un parent ne peut pas accéder à son refs de l'enfant). J'ai donc besoin d'un autre moyen de passer la valeur sélectionnée "up".

Ou, si cela n'est pas possible, un Nudge dans la bonne direction - je ne veux pas me retrouver avec une tonne de code non réutilisable (une des raisons pour lesquelles je suis passé à réagir était d'utiliser des mixins et de garder le code à un minimum sain et lisible).

13
George Oblapenko

Un parent peut accéder aux références de son enfant tant que vous définissez l'enfant comme référence à l'intérieur du parent, vous pouvez accéder à toutes les références à l'intérieur de cet enfant. Par exemple, quelque chose comme this.refs.childRef.refs.nestedChildRef. Cependant, c'est une très mauvaise idée à moins que vous ne lisiez que des informations (et que vous ne gériez pas les erreurs telles que les références, car le parent ne sait pas si la référence de l'enfant est définie correctement).

Mais heureusement, il existe une solution très simple. Vous avez raison, vous voudrez créer un composant enfant qui obtient ses propres données, plutôt qu'un mixage. Mais la façon dont vous gérez le retour de l'élément sélectionné au parent consiste simplement à transmettre une fonction onChange du parent comme vous le feriez à un <select> Intégré en réaction.

Voici un exemple de cette relation parent-enfant:

var MyParent = React.createClass({
    getInitialState: function() {
        return {
            childSelectValue: undefined
        }
    },
    changeHandler: function(e) {
        this.setState({
            childSelectValue: e.target.value
        })
    },
    render: function() {
        return (
            <div>
                <MySelect 
                    url="http://foo.bar"
                    value={this.state.childSelectValue}
                    onChange={this.changeHandler} 
                />
            </div>
        )
    }
});

var MySelect = React.createClass({
    propTypes: {
        url: React.PropTypes.string.isRequired
    },
    getInitialState: function() {
        return {
            options: []
        }
    },
    componentDidMount: function() {
        // get your data
        $.ajax({
            url: this.props.url,
            success: this.successHandler
        })
    },
    successHandler: function(data) {
        // assuming data is an array of {name: "foo", value: "bar"}
        for (var i = 0; i < data.length; i++) {
            var option = data[i];
            this.state.options.Push(
                <option key={i} value={option.value}>{option.name}</option>
            );
        }
        this.forceUpdate();
    },
    render: function() {
        return this.transferPropsTo(
            <select>{this.state.options}</select>
        )
    }
});

L'exemple ci-dessus montre un composant parent MyParent incluant un composant enfant MySelect auquel il transmet une URL, ainsi que tout autre accessoire valide pour un élément react <select> Standard. Utiliser la fonction this.transferPropsTo() intégrée de react pour transférer tous les accessoires vers l'élément <select> De l'enfant dans sa fonction de rendu.

Cela signifie que le parent définissant changeHandler comme gestionnaire onChange pour MySelect transmet cette fonction directement à l'élément <select> De l'enfant. Ainsi, lorsque la sélection change, l'événement est géré par le parent au lieu de l'enfant. Et à propos, c'est une manière parfaitement légale et légitime de faire les choses en React - car il suit toujours la philosophie descendante. Si vous y pensez, lorsque vous utilisez une fonction intégrée normale <select> De react, c'est exactement ce qui se passe. onChange n'est qu'une propriété pour <select>.

Il convient également de noter que si vous le souhaitez, vous pouvez "hyjack" le gestionnaire de changement pour ajouter votre propre logique personnalisée. Par exemple, ce que j'ai tendance à faire est d'envelopper mes champs <input type="text" /> Avec un composant d'entrée personnalisé qui prend tous les accessoires <input> Valides, mais prend également la fonction validator comme une propriété qui me permet de définir une fonction qui renvoie vrai/faux en fonction de la valeur entrée dans le champ <input>. Je peux le faire en définissant onChange dans le parent, pour l'enfant wrapper, mais ensuite dans l'enfant je définis mon propre onChange en interne qui vérifie que this.props.onChange À définir, et une fonction, puis exécute mon validateur et transmet la chaîne d'événements au parent en appelant this.props.onChange(e, valid). Donner au parent le même objet événement dans son gestionnaire onChange, mais aussi avec un argument booléen valid ajouté. Quoi qu'il en soit, je m'éloigne ...

J'espère que cela vous aidera :)

17
Mike Driver