Quản lý form trong React với Formik và Yup (P1)
Bài đăng này đã không được cập nhật trong 6 năm
Yêu cầu
Trước khi đọc bài viết này, bạn nên có những kiến thức cơ bản về việc sử dụng ReactJS
và biết cách xử lý form với ReactJS
. Nếu chưa biết thì bạn nên tìm hiểu hai vấn đề này trước ở đây
Mở đầu
Khi làm việc với các thư viện về front-end như ReactJS
, VueJS
hay AngularJS
thì việc quản lý các form là điều không thể thiếu. Quản lý form ở đây có thể hiểu là cách mà bạn quản lý thông tin mà người dùng nhập vào, tiến hành kiểm trả dữ liệu đầu vào đó (validate) sau đó hiển thị ra cho người dùng nếu có hoặc gửi (submit) lên server nếu không có thông tin người dùng là hợp lệ. Trong bài viết này, mình sẽ giới thiệu cho bạn về 2 thư viện mình hay sử dụng trong ReactJS
mà mình thường dùng để giải quyết công việc này một cách đơn giản nhất mà không cần phải tự code từ đầu.
Chuẩn bị
Trước khi bắt tay vào làm chúng ta cần cài đặt trước một số thứ như viện ngoài như sau:
- Material-UI - package hỗ trợ sẵn việc style cho các component React
- Formik - package dùng để quản lý form
- Yup - package dùng để validate dữ liệu
Bạn cũng có thể tham khảo bản đầy đủ của mình tại đây
Giao diện
Trước khi tiến hành việc quản lý thao tác form chúng ta sẽ tạo một project ReactJS
thông qua create-react-app
. Sau đó chúng ta tạo một folder components/Forms/
và một component có tên SignupForm.js
nằm trong folder này. Tiếp theo trong file src/App.js
ta sửa lại nộ dung ban đầu của nó về như sau:
import React, { Component } from 'react'
import SignupForm from './components/Forms/SignupForm';
class App extends Component {
render() {
return (
<SignupForm />
)
}
}
export default App
Phần <SignupForm /> chính là component mà chúng ta sẽ làm việc tiếp sau đó. Trong file src/index.js
ta cũng tiến hành xóa bớt những gì không cần thiết đi và chỉ để lại nội dung như sau:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
Cuối cùng ta xóa cả những file không cần thiết trong thư mục src/
đi và đây là kết quả về cấu trúc folder cuối cùng của chúng ta:
Để tiết kiệm thời gian nhưng vẫn có được giao điện nhìn đẹp đẹp nên mình sẽ sử dụng thư viện Material-UI của google. Nếu thích bạn có thể tự tìm hiểu cách sử dụng còn không bạn chỉ cần copy y nguyên phần code của mình để có được 1 form đẹp mắt:
import React, { Component } from 'react'
import Input from '@material-ui/core/Input'
import FormControl from '@material-ui/core/FormControl'
import InputLabel from '@material-ui/core/InputLabel'
import Button from '@material-ui/core/Button'
import Paper from '@material-ui/core/Paper'
import Grid from '@material-ui/core/Grid'
import FormControlLabel from '@material-ui/core/FormControlLabel'
import Checkbox from '@material-ui/core/Checkbox'
import Select from '@material-ui/core/Select'
import MenuItem from '@material-ui/core/MenuItem'
import FormHelperText from '@material-ui/core/FormHelperText'
import Typography from '@material-ui/core/Typography'
class SignupForm extends Component {
render() {
return (
<Grid container justify='center' alignContent='center'>
<Grid item xs={6} md={4}>
<Paper elevation={4} style={{ padding: '20px 15px', marginTop: '30px' }}>
<Typography variant="headline" gutterBottom>
Signup
</Typography>
<FormControl fullWidth margin='normal'>
<InputLabel>Username</InputLabel>
<Input name='username' fullWidth />
</FormControl>
<FormControl fullWidth margin='normal'>
<InputLabel>Email</InputLabel>
<Input name='email' fullWidth />
</FormControl>
<FormControl fullWidth margin='normal'>
<InputLabel>Password</InputLabel>
<Input fullWidth name='password' type='password' />
</FormControl>
<FormControl fullWidth margin='normal'>
<InputLabel>Plan</InputLabel>
<Select
displayEmpty
name='plan'
>
<MenuItem value='basic'>Basic</MenuItem>
<MenuItem value='advance'>Advance</MenuItem>
<MenuItem value='enterprise'>Enterprise</MenuItem>
</Select>
</FormControl>
<FormControlLabel
control={
<Checkbox />
}
label='Receive new letter'
/>
<FormControl fullWidth margin='normal'>
<Button
variant='extendedFab'
color='primary'
type='submit'
>
Signup
</Button>
</FormControl>
</Paper>
</Grid>
</Grid>
)
}
}
export default SignupForm
Và đây là kết quả bạn thu được với đoạn code trên: Đến đây là chúng ta đã hoàn thành bước chuẩn bị project cũng như giao diện cần thiết. Tiếp đến chúng ta sẽ bắt tay vào quản lý và xử lý dữ liệu trong form.
Formik & Yup
Khi bạn tham khảo link github của Formik ở ngay phân overview đã giới thiệu mục đích của việc dùng Formik
:
- Getting values in and out of form state
- Validation and error messages
- Handling form submission
Hiểu cơ bản là Formik
sẽ giúp chúng ta thực hiện 3 công việc thường xuyên dùng khi thao tác với form trong React
là:
- Đưa dữ liệu vào hoặc lấy dữ liệu nhập ra trong form dưới dạng props thông thường
- Kiểm tra tính hợp lệ của dữ liệu và hiển thị thông báo nếu lỗi
- Hỗ trợ việc gửi form lên server
Giá trị các field trong form của chúng ta nếu trước đây được lưu trong state của chính form component đó thì giờ đây, khi sử dụng Formik
thì giá trị của các form này sẽ do Formik
quản lý và truyền vào trong form component của chúng ta dưới dạng các props
.
Như mình đã nhắc đến ở đầu bài viết là bạn nên có những kiến thức cơ bản về thao tác với form trong React
trước khi bắt đầu đọc bài này để mang lại hiệu quả tốt hơn.
Đến đây chúng ta sẽ bắt đầu việc cài đặt Formik
thông qua npm
hoặc yarn
(nếu bạn đã cài yarn) :
npm i formik
hoặc
yarn add formik
Sau đó chúng ta sẽ tiến hành import
thêm nó ở ngay trên của phần chúng ta khai báo class SignupForm extends Component
như sau:
...
import { withFormik } from 'formik'
class SignupForm extends Component {
...
}
Để đơn giản hóa cho việc xử lý form, tạm thời chúng ta sẽ chỉ tập chung ta sẽ comment bớt các field khác và chỉ giữ lại field username và button signup. Đây là nội dung component SingupForm.js
sau khi đã loại bỏ bớt các thành phần:
import { withFormik } from 'formik'
class SignupForm extends Component {
render() {
return (
<Grid container justify='center' alignContent='center'>
<Grid item xs={6} md={4}>
<Paper elevation={4} style={{ padding: '20px 15px', marginTop: '30px' }}>
<Typography variant="headline" gutterBottom>
Signup
</Typography>
<FormControl fullWidth margin='normal'>
<InputLabel>Username</InputLabel>
<Input name='username' fullWidth />
</FormControl>
<FormControl fullWidth margin='normal'>
<Button
variant='extendedFab'
color='primary'
type='submit'
>
Signup
</Button>
</FormControl>
</Paper>
</Grid>
</Grid>
)
}
}
export default SignupForm
withFormik
ở đây chính là một Higher Order Component
dùng để bọc bên ngoài component của chúng ta đồng thời cung cấp cho component của chúng ta thêm một số tính năng mới. Ngay trươc phần export default SignupForm
chúng ta sẽ thêm đoạn code sau đồng thời sửa lại luôn phần export
:
const FormikForm = withFormik({
mapPropsToValues() { // Init form field
return {
username: '',
}
},
})(SignupForm)
export default FormikForm
Ở đây FormikForm = withFormik({})
chính là phần chúng ta khai báo HOCs
của Formik
với nội dung bên trong đó chính là phần cài đặt riêng theo form của chúng ta. Về các option mà Formik
hỗ trợ bạn có thể đọc ở đây. Bài viết của mình sẽ chỉ giới thiệu đến các bạn một số option cơ bản nhất và cũng là đủ để dùng cho các form thông thường của bạn.
Trong đoạn code mà chúng ta mới thêm vào, option đầu tiên mà chúng ta sử dụng là mapPropsToValues() {}
, đây là nơi chúng ta sử dụng để khởi tạo giá trị cho các field trong form của chúng ta, ở đây là cho field username
. Lưu ý ở đây bạn cần đặt phần name
của input field trong form của bạn giống với tên của giá trị mà bạn khởi tạo trong mapPropsToValues() {}
. Cụ thể như sau:
# Nếu phần input của chúng ta có name=username
<Input name='username' fullWidth />
# Thì đồng nghĩa với việc trong hàm mapPropsToValues()
# ta cũng cần khai báo key của object trong hàm return là username
mapPropsToValues() { // Init form field
return {
username: '',
}
},
Sở dĩ chúng ta cần khai báo trùng như vậy vì Formik
sẽ hỗ trợ sẵn cho chúng ta việc cập nhật giá trị mà chúng ta nhập vào các field input mà không cần phải tạo các hàm handleInputChange
thông thường nữa. Vì các giá trị trong Formik
truyền qua component của chúng ta dưới dạng props
nên chúng ta có thể truy cập nó thông qua this.props.values.username
với:
this.props.values
là props chứa đựng giá trị của các fieldusername
là tên của field
Sau đây chúng ta sẽ tiến hành tryền giá trị này vào field username của chúng ta như sau:
<FormControl fullWidth margin='normal'>
<InputLabel>Username</InputLabel>
<Input
fullWidth
name='username'
value={this.props.values.username} />
</FormControl>
Để kiểm tra xem việc cài đặt đã chính xác chưa ta có thể thử thay đổi giá trị khởi tạo của username
trong hàm mapPropsToValues
từ rỗng thành giá trị bất kì để xem trong giao diện có thay đổi theo đó không như sau:
mapPropsToValues() { // Init form field
return {
username: 'abc',
}
},
Khi chuyển lại giao diện nếu chúng ta thấy có sẵn 3 kí tự abc
xuất hiện ở trường username
có nghĩa là chúng ta đã cài đặt thành công:
Lúc này, nếu bạn thử thay đổi kí nội dung username
sẽ thấy chúng ta hoàn toàn không nhập được thêm cũng như không xóa được các kí tự đi vì chúng ta chưa khai báo hàm để đẩm nhiệm công việc này. Nếu bạn còn nhớ documents của React
liên quan đến form thì chắc hẳn bạn sẽ nhớ phần code phục vụ cho việc cập nhật giá trị của các input field sẽ có dạng như sau:
state = {
username: '',
password: '',
}
handleInputChange(event) {
const target = event.target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<form>
<input name='username' value={this.state.username} onChange={this.handleInputChange} />
<input name='password' value={this.state.password} onChange={this.handleInputChange} />
</form>
)
}
Tuy nhiên vì Formik
đã định nghĩa sẵn hàm handleInputChange
cho bạn rồi nên bạn chỉ cần thêm nó vào field của mình như sau:
<FormControl fullWidth margin='normal'>
<InputLabel>Username</InputLabel>
<Input
fullWidth
name='username'
value={values.username}
onChange={this.props.handleChange} />
</FormControl>
Bây giờ bạn đã có thể hoàn toàn nhập thêm hoặc xóa bớt kí tự trong input filed của mình và mọi thay đổi đã được Formik
đảm nhiệm thay cho chúng ta qua props.handleChange
. Lưu ý là bạn phải có phần name
tương ứng với giá trị khởi tạo bạn đầu trong mapPropsToValues()
. Tiêp theo chúng ta sẽ tiến hành kiểm tra nội dung nhập vào có thỏa mãn yêu cầu mà chúng ta đặt ra không.
Để thực hiện việc này, chúng ta sẽ cài đặt thêm package Yup như sau:
npm install yup
hoặc
yarn add yup
Bạn có thể hiểu cơ bản thì Yup là package hỗ trợ chúng ta việc kiểm tra dữ liệu nhập vào theo từng kí tự chúng ta nhập vào form. Sau khi cài đặt Yup
, ta tiếp tục thêm option liên quan đến kiểm tra nội dung dữ liệu nhập vào là validationSchema
vào nội dung của withFormik({})
mà chúng ta đã khai báo ở trên như sau:
...
import { withFormik } from 'formik'
import * as Yup from 'yup'
class SignupForm extends Component {
...
}
const FormikForm = withFormik({
mapPropsToValues() { // Init form field
return {
username: '',
}
},
validationSchema: Yup.object().shape({ // Validate form field
username: Yup.string()
.required('Username is required')
.min(5, 'Username must have min 5 characters')
.max(10, 'Username have max 10 characters'),
}),
})(SignupForm)
export default FormikForm
Ở đây, bên trong hàm Yup.object().shape({})
là nơi chúng ta sẽ tiến hành khai báo các luật đặt ra cho fied của chúng ta. Cụ thể ở đây ta đang khai báo như sau:
- username là dạng chuỗi kí tự
string(message)
- username không được để trống
required(message)
- username có tối thiểu 5 kí tự
min(5, message)
- username có tối đa 10 kí tự
max(10, message)
- message ở đây chính lại nội dung thông báo lỗi nếu vi phạm.
Để sử lấy được nội dung lỗi, chúng ta sẽ truy cập vào một props
đặc biệt mà Formik
cung cấp cho chúng ta là errors
. Bạn có thể hiểu nôm na đẫy là object chứa các message báo lỗi liên vi phạm quy ước cho các field của chúng ta và có thể truy cập vào cụ thể từng field theo cú pháp this.props.errors.[field_name]
hay ở đây chính là this.props.errors.username
. Trong giao diện chứa field username chúng ta sẽ bổ sung thêm 1 component của Material-UI
để hỗ trợ việc hiển thị lỗi này như sau:
<FormControl fullWidth margin='normal'>
<InputLabel>Username</InputLabel>
<Input
fullWidth
name='username'
value={values.username}
onChange={this.props.handleChange} />
<FormHelperText>{this.props.errors.username}</FormHelperText>
</FormControl>
Sau đấy bạn quay lại form và thử nhập đúng một chữ cái sẽ thu được kết quả như sau:
Nội dung message bên dưới đúng với nội dung mà bạn định nghĩa ở trên và nó sẽ chỉ mất đi khi bạn nhập thỏa mãn nội dung đặt ra đồng thời với mỗi kí tự bạn nhập vào thì nội dung message sẽ thay đổi luôn và báo ra lỗi đầu tiên mà bạn mắc phải theo thứ tự khai báo ở trên. Chúng ta thử tiếp tục nhập cho đến khi vượt quá 10 kí tự sẽ thu được lỗi mới:
Để tăng cường tính thẩm mỹ thì bạn có thể thêm vào một số nội dung của Material-UI
như sau:
<FormControl fullWidth margin='normal' error={!!this.props.errors.username}>
<InputLabel>Username</InputLabel>
<Input
fullWidth
name='username'
value={this.props.values.username}
onChange={this.props.handleChange} />
<FormHelperText>{this.props.errors.username}</FormHelperText>
</FormControl>
Bây giờ bạn quay lại giao diện để test sẽ thấy đẹp hơn 1 tí
Kết bài
Mong rằng qua P1 của bài viết, các bạn đã nắm được cách sử dụng cơ bản đôi với Formik & Yup
. Các field tiếp theo mình sẽ không hướng dẫn các bạn tự làm luôn ở part này mà mình muốn để các bạn tự làm thử để có thể hiểu được rõ hơn cách làm. Ở phần tiếp theo, mình sẽ đưa ra hướng dẫn các bạn xử lý nốt các field còn lại cũng như tối ưu lại code của mình, các bạn hãy chú ý đón đọc.
All rights reserved