React.js. Управляемые компоненты

29.07.2021

Теги: FrontendJavaScriptReact.jsWeb-разработкаТеорияФорма

В React существует два различных подхода для управления формами:

  • Элемент формы input, управляемый React — это управляемый компонент
  • Неуправляемый компонент работает как обычный элемент формы вне React

1. Неуправляемый компонент

Неуправляемый компонент работает как обычный элемент формы, получить введенные пользователем значения можно с помощью ref.

class Form extends React.Component {
    constructor(props) {
        super(props);
        this.inputRefer = React.createRef();
    }

    handleSubmit = (event) => {
        alert('Отправленное имя: ' + this.inputRefer.current.value);
        event.preventDefault();
    }

    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <input type="text" name="name" ref={this.inputRefer} />
                <input type="submit" value="Отправить" />
            </form>
        );
    }
}

2. Управляемый компонент

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

<input value={someValue} onChange={handleChange} />

Какой-то компонент хранит состояние полей ввода в своем state и передает их элементам формы через пропсы.

class Form extends React.Component {
    constructor() {
        super();
        this.state = {
            name: '',
        };
    }

    handleChange = (event) => {
        this.setState({name: event.target.value});
    };

    render() {
        return (
            <form>
                <input
                    type="text"
                    name="name"
                    value={this.state.name}
                    onChange={this.handleChange}
                />
                <input type="submit" value="Отправить" />
            </form>
        );
    }
}

Атрибут value всегда показывает значение this.state.name. Состояние компонента Form стало единственным «источником истины». Каждое нажатие клавиши вызывает handleChange и обновляет state компонента — а значит, будет обновляться и значение value.

Если полей формы несколько, нет необходимости создавать callback-функцию для каждого. Через event.target мы можем получить не только value, но и name.

class Form extends React.Component {
    constructor() {
        super();
        this.state = {
            name: '',
            surname: '',
            message: '',
        };
    }

    handleChange = (event) => {
        this.setState({[event.target.name]: event.target.value});
    };

    render() {
        return (
            <form>
                <input
                    type="text"
                    name="name"
                    value={this.state.name}
                    onChange={this.handleChange}
                    placeholder="Имя"
                />
                <input
                    type="text"
                    name="surname"
                    value={this.state.surname}
                    onChange={this.handleChange}
                    placeholder="Фамилия"
                />
                <textarea
                    name="message"
                    value={this.state.message}
                    onChange={this.handleChange}
                    onBlur={this.validateField}
                    placeholder="Сообщение"
                />
                <input type="submit" value="Отправить" />
            </form>
        );
    }
}

Здесь мы добавили текстовое поле textarea — работа с ним ничем не отличается от работы с полем input.

2.1. Валидация полей формы

Добавим для нашей формы валидацию полей — при потере фокуса будем вызывать функцию validateField:

class Form extends React.Component {
    state = {
        name: '',
        surname: '',
        message: '',
    }

    handleChange = (event) => {
        this.setState({[event.target.name]: event.target.value});
    }

    validateField = (event) => {
        let name = event.target.name;
        let value = this.state[name];
        switch(name) {
            case 'name':
                console.log('Проверка имени пользователя');
                break;
            case 'surname':
                console.log('Проверка фамилии пользователя');
                break;
            case 'message':
                console.log('Проверка сообщения пользователя');
                break;
            default:
                console.log('Что-то пошло не так');
        }
    }

    render() {
        return (
            <form>
                <input
                    type="text"
                    name="name"
                    value={this.state.name}
                    onChange={this.handleChange}
                    onBlur={this.validateField}
                    placeholder="Имя"
                />
                <input
                    type="text"
                    name="surname"
                    value={this.state.surname}
                    onChange={this.handleChange}
                    onBlur={this.validateField}
                    placeholder="Фамилия"
                />
                <textarea
                    name="message"
                    value={this.state.message}
                    onChange={this.handleChange}
                    onBlur={this.validateField}
                    placeholder="Сообщение"
                />
                <input type="submit" value="Отправить" />
            </form>
        );
    }
}

2.2. Выпадающий список select

Посмотрим, как работать с выпадающим списком <select>, чтобы выбранный <option> сохранялся в state компонента Form.

class Form extends React.Component {
    state = {
        name: "",
        surname: "",
        lang: "",
    }

    handleChange = (event) => {
        this.setState({[event.target.name]: event.target.value});
    }

    validateField = (event) => {
        let elem = event.target;
        let name = elem.name;
        let value = this.state[name];
        switch(name) {
            case 'name': // имя пользователя
                if (value === "") {
                    elem.style.border = "1px solid red"
                } else {
                    elem.style.border = ""
                }
                break;
            case 'surname': // фамилия пользователя
                if (value === "") {
                    elem.style.border = "1px solid red"
                } else {
                    elem.style.border = ""
                }
                break;
            case 'lang': // выбор языка из списка
                    if (value === "") {
                        elem.style.border = "1px solid red"
                    } else {
                        elem.style.border = ""
                    }
                break;
            default:
                console.log('Что-то пошло не так');
        }
    }

    render() {
        return (
            <form>
                <input
                    type="text"
                    name="name"
                    value={this.state.name}
                    onChange={this.handleChange}
                    onBlur={this.validateField}
                    placeholder="Имя"
                />
                <input
                    type="text"
                    name="surname"
                    value={this.state.surname}
                    onChange={this.handleChange}
                    onBlur={this.validateField}
                    placeholder="Фамилия"
                />
                <select
                    name="lang"
                    value={this.state.lang}
                    onChange={this.handleChange}
                    onBlur={this.validateField}
                >
                    <option value="">Выберите</option>
                    <option value="123">Английский</option>
                    <option value="456">Немецкий</option>
                    <option value="789">Французский</option>
                </select>
                <input type="submit" value="Отправить" />
            </form>
        );
    }
}

Здесь мы работаем с выбранным значением <select> как со строкой ("123", "456", "789"), но можем работать и как с числом.

class Form extends React.Component {
    state = {
        name: "",
        surname: "",
        lang: 0
    }

    handleChange = (event) => {
        if (event.target.tagName === "SELECT") {
            this.setState({[event.target.name]: parseInt(event.target.value)});
        } else {
            this.setState({[event.target.name]: event.target.value});
        }
    }

    validateField = (event) => {
        let elem = event.target;
        let name = elem.name;
        let value = this.state[name];
        switch(name) {
            case 'name': // имя пользователя
                if (value === "") {
                    elem.style.border = "1px solid red";
                } else {
                    elem.style.border = "";
                }
                break;
            case 'surname': // фамилия пользователя
                if (value === "") {
                    elem.style.border = "1px solid red";
                } else {
                    elem.style.border = "";
                }
                break;
            case 'lang': // выбор языка из списка
                    if (value === 0) {
                        elem.style.border = "1px solid red";
                    } else {
                        elem.style.border = "";
                    }
                break;
            default:
                alert('Что-то пошло не так');
        }
    }

    render() {
        return (
            <form>
                <input
                    type="text"
                    name="name"
                    value={this.state.name}
                    onChange={this.handleChange}
                    onBlur={this.validateField}
                    placeholder="Имя"
                />
                <input
                    type="text"
                    name="surname"
                    value={this.state.surname}
                    onChange={this.handleChange}
                    onBlur={this.validateField}
                    placeholder="Фамилия"
                />
                <select
                    name="lang"
                    value={this.state.lang}
                    onChange={this.handleChange}
                    onBlur={this.validateField}
                >
                    <option value="0">Выберите</option>
                    <option value="123">Английский</option>
                    <option value="456">Немецкий</option>
                    <option value="789">Французский</option>
                </select>
                <input type="submit" value="Отправить" />
            </form>
        );
    }
}

2.3. Один флажок checkbox

Посмотрим, как работать с одним флажком checkbox, чтобы сохранять его состояние (отмечен или нет) в state компонента Form.

class Form extends React.Component {
    state = {
        name: "",
        surname: "",
        lang: 0,
        agree: false,
    }

    handleChange = (event) => {
        // текстовое поле input (имя, фамилия)
        if (event.target.tagName === "INPUT" && event.target.type === "text") {
            this.setState({[event.target.name]: event.target.value});
        }
        // выпадающий список select (выбор языка)
        if (event.target.tagName === "SELECT") {
            this.setState({[event.target.name]: parseInt(event.target.value)});
        }
        // флажок checkbox (согласие с условиями)
        if (event.target.tagName === "INPUT" && event.target.type === "checkbox") {
            this.setState({[event.target.name]: event.target.checked});
        }
    }

    validateField = (event) => {
        let elem = event.target;
        let name = elem.name;
        let value = this.state[name];
        switch(name) {
            case 'name': // имя пользователя
                if (value === "") {
                    elem.style.border = "1px solid red";
                } else {
                    elem.style.border = "";
                }
                break;
            case 'surname': // фамилия пользователя
                if (value === "") {
                    elem.style.border = "1px solid red";
                } else {
                    elem.style.border = "";
                }
                break;
            case 'lang': // выбор языка из списка
                    if (value === 0) {
                        elem.style.border = "1px solid red";
                    } else {
                        elem.style.border = "";
                    }
                break;
            case 'agree': // согласие с условиями
                    if (!value) {
                        elem.parentElement.style.color = "red";
                    } else {
                        elem.parentElement.style.color = "";
                    }
                break;
            default:
                alert('Что-то пошло не так');
        }
    }

    render() {
        return (
            <form>
                <input
                    type="text"
                    name="name"
                    value={this.state.name}
                    onChange={this.handleChange}
                    onBlur={this.validateField}
                    placeholder="Имя"
                />
                <input
                    type="text"
                    name="surname"
                    value={this.state.surname}
                    onChange={this.handleChange}
                    onBlur={this.validateField}
                    placeholder="Фамилия"
                />
                <select
                    name="lang"
                    value={this.state.lang}
                    onChange={this.handleChange}
                    onBlur={this.validateField}
                >
                    <option value="0">Выберите</option>
                    <option value="123">Английский</option>
                    <option value="456">Немецкий</option>
                    <option value="789">Французский</option>
                </select>
                <label>
                    <input
                        type="checkbox"
                        name="agree"
                        checked={this.state.agree}
                        onChange={this.handleChange}
                        onBlur={this.validateField}
                    /> Согласен с условиями
                </label>
                <input type="submit" value="Отправить" />
            </form>
        );
    }
}

2.4. Радио-кнопки radio

Наша форма стала слишком большой, так что для следующего примера оставим в ней только радио-кнопки:

class Form extends React.Component {
    state = {
        langs: [
            {id: 123, value: 'Английский', checked: false},
            {id: 456, value: 'Немецкий', checked: false},
            {id: 789, value: 'Французский', checked: false},
        ]
    }

    handleChange = (event) => {
        const id = parseInt(event.target.value);
        this.state.langs.forEach((lang, index, langs) => {
            if (lang.id === id) {
                langs[index].checked = true;
            } else {
                langs[index].checked = false;
            }
        });
        this.setState({langs: this.state.langs});
    }

    handleSubmit = (event) => {
        event.preventDefault();
        console.log(this.state.langs);
    }

    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                {this.state.langs.map(lang =>
                    <label key={lang.id}>
                        <input
                            type="radio"
                            name="langs"
                            value={lang.id}
                            checked={lang.checked}
                            onChange={this.handleChange}
                        /> {lang.value}
                    </label>
                )}
                <input type="submit" value="Отправить" />
            </form>
        );
    }
}

Когда отмечена вторая радио-кнопка, при попытке отправить форму, console.log() покажет состояние каждой кнопки:

[
    {"id": 123, "value": "Английский", "checked": false},
    {"id": 456, "value": "Немецкий", "checked": true},
    {"id": 789, "value": "Французский", "checked": false}
]

2.5. Несколько флажков checkbox

Часто флажки объединяются в группу с одинаковым значением атрибута name — это позволяет выбрать несколько значений. Мы должны отследить состояние каждого флажка и сохранить его в state компонента Form. Тогда при ajax-отправке данных формы сможем отправить на сервер только те флажки, которые были отмечены пользователем.

class Form extends React.Component {
    state = {
        langs: [
            {id: 123, value: 'Английский', checked: false},
            {id: 456, value: 'Немецкий', checked: false},
            {id: 789, value: 'Французский', checked: false},
        ]
    }

    handleChange = (event) => {
        const id = parseInt(event.target.value);
        this.state.langs.forEach((lang, index, langs) => {
            if (lang.id === id) langs[index].checked = event.target.checked;
        });
        this.setState({langs: this.state.langs});
    }

    handleSubmit = (event) => {
        event.preventDefault();
        console.log(this.state.langs);
    }

    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                {this.state.langs.map(lang =>
                    <label key={lang.id}>
                        <input
                            type="checkbox"
                            name="langs"
                            value={lang.id}
                            checked={lang.checked}
                            onChange={this.handleChange}
                        /> {lang.value}
                    </label>
                )}
                <input type="submit" value="Отправить" />
            </form>
        );
    }
}

Когда отмечены первый и третий флажки, при попытке отправить форму, console.log() покажет состояние каждого флажка:

[
    {"id": 123, "value": "Английский", "checked": true},
    {"id": 456, "value": "Немецкий", "checked": false},
    {"id": 789, "value": "Французский", "checked": true}
]
Поскольку у всех флажков одинаковое имя, браузер отправит их на сервер в виде langs=123&langs=789. Сервер должен правильно обработать такое значение, чтобы последнее значение не затерло все предыдущие.

2.6. Список select multiple

Список мы уже рассматривали, но пропустили вариант, который допускает выбор нескольких элементов — это возможно, когда у списка есть атрибут multiple.

class Form extends React.Component {
    state = {
        langs: [
            {id: 123, value: 'Английский', selected: false},
            {id: 456, value: 'Немецкий', selected: false},
            {id: 789, value: 'Французский', selected: false},
        ]
    }

    handleChange = (event) => {
        const options = event.target.options;
        this.state.langs.forEach((lang, index, langs) => {
            langs[index].selected = options[index].selected;
        });
        this.setState({langs: this.state.langs});
    }

    handleSubmit = (event) => {
        event.preventDefault();
        console.log(this.state.langs);
    }

    render() {
        const selected = [];
        this.state.langs.forEach(lang => {
            if (lang.selected) selected.push(lang.id);
        });
        return (
            <form onSubmit={this.handleSubmit}>
                <select
                    name="langs"
                    value={selected}
                    onChange={this.handleChange}
                    multiple={true}
                    size="3"
                >
                {this.state.langs.map(lang =>
                    <option key={lang.id} value={lang.id}>{lang.value}</option>
                )}
                </select>
                <input type="submit" value="Отправить" />
            </form>
        );
    }
}

Когда отмечены первый и третий элементы списка, при попытке отправить форму, console.log() покажет состояние каждого элемента:

[
    {"id": 123, "value": "Английский", "selected": true},
    {"id": 456, "value": "Немецкий", "selected": false},
    {"id": 789, "value": "Французский", "selected": true}
]

Поиск: JavaScript • React.js • Web-разработка • Frontend • Теория • Форма • Управляемый компонент

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.