Эта запись изначально была опубликована в моем блоге
Обработка форм — огромная тема веб-разработки. Есть почти только 2 способа работы с формой, в зависимости от библиотеки/фреймворка, который вы выбрали для использования, и в зависимости от стороны, с которой вы пытаетесь справиться с формой (клиент или сервер):
- Те, которые включают обработку формы компонента. Этот компонент обычно делает много волшебства и заставляет вас думать о своей форме так, как она будет обрабатываться этим компонентом.
- Те, которые позволяют вам делать всю работу. А потом вы чувствуете, что это нонсенс — управлять своей формой на таком низком уровне самостоятельно.
Простой вариант использования в React
Пока вы работаете с простой формой в React, все остается довольно простым. В основном вам просто нужно беспокоиться о вашем методе onSubmit
, который будет вызывать ваш API для отправки данных формы POST.
Вот базовый пример формы, публикующей сниппет (заголовок и текст) с использованием React, Material UI и Redux (после публикации данных метод addSnippet
отправляет событие, чтобы обновить другой компонент )
import React from 'react';
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import TextField from 'material-ui/TextField';
import {addSnippet} from "../../../actions/snippets";
import {connect} from "react-redux";
import { bindActionCreators } from 'redux';
class AddSnippet extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit = (event) => {
event.preventDefault();
const form = new FormData(event.target);
const data = {
"title": form.get("title"),
"snippet": form.get("snippet")
};
this.props.addSnippet(data);
};
render() {
const actions = [
<FlatButton
label="Cancel"
primary={true}
onClick={this.handleClose}
key="cancel"
/>,
<FlatButton
type="submit"
label="Submit"
primary={true}
keyboardFocused={true}
key="submit"
/>,
];
return (
<div>
<Dialog
title="Adding a snippet"
modal={false}
open={this.state.open}
onRequestClose={this.handleClose}>
<form method="POST" onSubmit={this.handleSubmit}>
<TextField
hintText="My awesome snippet"
floatingLabelText="Title"
name="title"
/><br />
<TextField
hintText="<?php echo 'Hello World'; ?>"
floatingLabelText="Snippet"
multiLine={true}
rows={5}
rowsMax={10}
fullWidth={true}
name="snippet"
/><br />
<div style={{ textAlign: 'right', padding: 8, margin: '24px -24px -24px -24px' }}>
{actions}
</div>
</form>
</Dialog>
</div>
);
}
}
const mapDispatchToProps = dispatch => (bindActionCreators({
addSnippet
}, dispatch));
export default connect(
null,
mapDispatchToProps
)(AddSnippet)
Но потом… вы, вероятно, захотите сделать какую-то проверку.
Первый шаг — выполнить проверку на стороне клиента, например: если поле является обязательным, вы наверняка захотите проверить, не пусто ли поле, прежде чем вызывать API. В этом примере нам пришлось бы делать это вручную. Инициализация состояния с полями, а затем проверка состояния перед вызовом API может быть хорошей идеей. Но все же… Это требует времени и может быть источником ошибок/багов.
Второй шаг — проверка на стороне сервера. Также: вызов вашего API и проверка кода состояния › 299. Затем получите возвращенную полезную нагрузку, скачкообразно, есть общая схема для ошибок проверки и сопоставьте ошибки, отправленные API, с вашими полями. Эта часть немного сложна, а также является источником ошибок/ошибок.
Проверка формы стала проще с Formik
Formik — это реактивная библиотека, которая помогает вам делать основы с формами, не вводя никакой магии. Вы можете рассматривать его как набор инструментов, которые могут вам понадобиться при обработке таких форм, как: handleSubmit
, handleChange
, handleBlur
, setErrors
и т. д.
Пример с комментариями вы найдете ниже. Мне потребовалось время, чтобы понять, как правильно обрабатывать ошибки, поступающие от API, поэтому в этом примере также документирован вызов API. Идея состоит в том, чтобы понять все это здесь.
В этом примере используются Formik, Redux, Yup для проверки на стороне клиента и Material UI для дизайна. Форма находится в Dialog
(Диалог), который открывается, когда пользователь нажимает на FloatingActionButton
(FloatingActionButton)
Имейте в виду, что в случае ошибок API должен возвращать полезную нагрузку, структурированную следующим образом:
{
"errors": [
{
"field": "title",
"error": "This field is required"
}
]
}
Мой файл компонента: AddSnippet
import React from 'react';
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import FloatingActionButton from 'material-ui/FloatingActionButton';
import ContentAdd from 'material-ui/svg-icons/content/add';
import TextField from 'material-ui/TextField';
import {addSnippet} from "../../../actions/snippets";
import {connect} from "react-redux";
import { bindActionCreators } from 'redux';
import { Formik } from 'formik';
import Yup from 'yup'
class AddSnippet extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
};
this.handleClose = this.handleClose.bind(this);
}
handleOpen = () => {
this.setState({open: true});
};
handleClose = () => {
this.setState({open: false});
};
render() {
return (
<div>
<FloatingActionButton secondary={true} onClick={this.handleOpen} style={{
margin: 0,
top: 'auto',
right: 20,
bottom: 20,
left: 'auto',
position: 'fixed',
}}>
<ContentAdd />
</FloatingActionButton>
<Dialog
title="Adding a snippet"
modal={false}
open={this.state.open}
onRequestClose={this.handleClose}>
<Formik
initialValues={{title: '', snippet: ''}}
onSubmit={async (values, {setFieldError}) => {
try {
await this.props.addSnippet(values); // Call the api
this.handleClose();
} catch (errors) { // Catch status code > 299
errors.forEach( err => {
setFieldError(err.field, err.error); // Map errors to fields
});
}
}}
validationSchema={Yup.object().shape({
title: Yup.string() //Client side validation for field "title"
.min(3, 'Title must be at least 3 characters long.')
.required('Title is required.'),
snippet: Yup.string() //Client side validation for field "snippet"
.min(3, 'Snippet must be at least 3 characters long.')
.required("Snippet is required"),
})}
component={ this.form }
/>
</Dialog>
</div>
)
}
form = ({handleSubmit, handleChange, handleBlur, values, errors}) => {
return (
<form method="POST" onSubmit={handleSubmit}>
<TextField
hintText="My awesome snippet"
floatingLabelText="Title"
name="title"
onChange={handleChange} //By default client side validation is done onChange
onBlur={handleBlur} //By default client side validation is also done onBlur
value={values.title}
errorText={errors.title} //Error display
/><br />
<TextField
hintText="<?php echo 'Hello World'; ?>"
floatingLabelText="Snippet"
multiLine={true}
rows={5}
rowsMax={10}
fullWidth={true}
name="snippet"
onChange={handleChange}
onBlur={handleBlur}
value={values.snippet}
errorText={errors.snippet} //Error display
/><br />
<div style={{ textAlign: 'right', padding: 8, margin: '24px -24px -24px -24px' }}>
<FlatButton
label="Cancel"
primary={true}
onClick={this.handleClose}
key="cancel"
/>
<FlatButton
type="submit"
label="Submit"
primary={true}
keyboardFocused={true}
key="submit"
/>
</div>
</form>
);
}
}
const mapDispatchToProps = dispatch => (bindActionCreators({
addSnippet
}, dispatch));
//Connect the component to the store. See Redux.
export default connect(
null,
mapDispatchToProps
)(AddSnippet)
Файл моих действий (см. Redux):
import {postSnippet} from '../../api/snippets'
import { receiveOneSnippet } from '../'
export function addSnippet(data) {
return async function (dispatch) {
try {
// If status code == 201 the API return the new created object.
const response = await postSnippet(data);
//This object is dispatch to the store in order to update another component
dispatch(receiveOneSnippet(response))
} catch (err) {
//If status code > 299 the payload is catch here.
throw err.errors;
}
}
}
Где действительно выполняется мой вызов API (в отдельном файле):
export async function postSnippet(data) {
try {
const response = await fetch(`mydomain.com/snippets/`, {
method: 'POST',
body: JSON.stringify(data),
});
//If status code > 299
if (!response.ok) {
throw response;
}
return await response.json();
} catch (err) {
//Throw the return payload
throw await err.json()
}
}
Результат :
Первоначально опубликовано на http://le-gall.bzh/post/form-validation-with-formik/.