Создание элементов <select> в React JS

Я переписываю пользовательский интерфейс для своего веб-приложения в response.js, и я немного озадачен следующей проблемой.

У меня есть страница, на которой отображаются данные, полученные с помощью запроса AJAX, а ниже отображается форма для отправки новых данных. Все хорошо.

Теперь я хочу добавить элемент <select> в форму и извлечь значения из другого местоположения (url).

Текущий код (без <select>) выглядит так (упрощенный бит, но все рабочие детали одинаковы, в основном это следует за учебником на сайте 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')
        );
    });

Теперь я могу добавить элемент select, добавив getDataMixin to TaskForm, извлекая данные и создав список возможных параметров, но мне нужно будет иметь формы со многими списками, и подход не будет Кажется, что он масштабируется (из-за коллизий имен, или мне нужно использовать что-то другое, кроме mixins).

Так что, хотя я создаю отдельный React class, который просто имеет getDataMixin, получит URL-адрес API через родительский параметр props и отобразит элемент <select>; и используйте этот класс внутри формы. Но я понятия не имею, как получить доступ к выбранному значению (поскольку родитель не может получить к нему доступ дочерний refs). Поэтому мне нужен другой способ передать выбранное значение "вверх".

Или, в случае, если это невозможно, подталкивание в правильном направлении - я не хочу заканчивать тонну кода без повторного использования (часть причины, по которой я переключился на реакцию, заключалась в использовании mixins и сохраните код в разумном и удобочитаемом минимуме).

Ответ 1

Родитель может получить к нему дочерние refs, пока вы устанавливаете дочерний элемент как ref внутри родителя, вы можете получить доступ к любым refs внутри этого дочернего элемента. Так, например, что-то вроде this.refs.childRef.refs.nestedChildRef. Однако это действительно плохая идея, если вы просто не читаете информацию (и обрабатываете ошибки, такие как refs not existing правильно, так как родитель не знает, правильно ли установлен ref ref ref).

Но, к счастью, существует очень простое решение. Вы правы, вам нужно создать дочерний компонент, который получает свои собственные данные, а не mixin. Но как вы обрабатываете выделение выделенного элемента родительскому объекту, просто передайте функцию onChange из родительского элемента, как если бы вы вставляли встроенный <select> в реакцию.

Вот пример этого родительского отношения:

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

В приведенном выше примере показан родительский компонент MyParent, включающий дочерний компонент MySelect, который передает URL-адрес вместе с любыми другими реквизитами, которые действительны для стандартного элемента реакции <select>. Использование реакции встроенной функции this.transferPropsTo() для передачи всех реквизитов дочернему элементу <select> в его функции рендеринга.

Это означает, что родительский элемент, определяющий changeHandler в качестве обработчика onChange для MySelect, передает эту функцию непосредственно дочернему элементу <select>. Поэтому, когда выбор изменяет, событие обрабатывается родителем, а не дочерним. И, кстати, это законно законный и законный способ делать что-то в Реактите - поскольку он все еще следует философии сверху вниз. Если вы думаете об этом, когда вы используете обычный встроенный <select>, чтобы реагировать, это именно то, что происходит. onChange является просто свойством для <select>.

Также стоит отметить, что если вам нравится, вы можете "hyjack" обработчика изменений добавить свою собственную логику. Например, то, что я обычно делаю, это переносить мои поля <input type="text" /> с помощью специального входного компонента, который принимает любые и все допустимые реквизиты <input>, но также принимает функцию validator как свойство, которое позволяет мне определить функцию, которая возвращает true/false на основе значения, введенного в поле <input>. Я могу сделать это, указав onChange в родительском, для дочернего элемента оболочки, но затем в дочернем я определяю свой собственный onChange внутри, который проверяет на this.props.onChange, который будет определен, и функцию, затем запускает мой валидатор и передает цепочку событий обратно родительскому объекту, вызывая this.props.onChange(e, valid). Предоставление родительскому объекту в нем одного и того же объекта события onChange, но также с добавленным логическим аргументом valid. Во всяком случае, я отвлекаюсь...

Надеюсь, это поможет вам:)