Giới thiệu
Cài đặt
Ở đây mình sẽ sử dụng them react-router-dom, material-ui và react-hook-form
npx create-react-app learn-reactjs
npminstall --save react-hook-form
npminstall --save react-router-dom
npminstall @reduxjs/toolkit react-redux
npminstall --save axios
cd learn-reactjs
npm start
Tạo các thư mục
learn-reactjs
├─ build
├─ node_modules
├─ public
└─ src
├─ api
│ ├─ axiosClient.js
│ └─ userApi.js
├─ constants
│ └─ storage-keys.js
├─ app
│ └─ store.js
├─ components
│ └─ form-control
│ ├─ InputField
│ │ └─ index.jsx
│ └─ PasswordField
│ └─ index.jsx
└─ features
│ └─ Auth
│ ├─ userSlice.js
│ ├─ components
│ │ ├─ Login.jsx
│ │ └─ LoginForm.jsx
│ ├─ page
│ │ └─ LoginPage.jsx
│ └─ index.jsx
├─ App.css
├─ App.js
└─ index.js
App
Chỉnh sửa file index.js
import React from'react';import ReactDOM from'react-dom';import{ BrowserRouter }from'react-router-dom';import App from'./App';import'./index.css';import reportWebVitals from'./reportWebVitals';import{ Provider }from'react-redux';import store from'./app/store';
ReactDOM.render(<React.StrictMode><Provider store={store}><BrowserRouter><App /></BrowserRouter></Provider></React.StrictMode>,
document.getElementById('root'));// If you want to start measuring performance in your app, pass a function// to log results (for example: reportWebVitals(console.log))// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitalsreportWebVitals();
Chỉnh sửa file app.js
import{ Redirect, Route, Switch }from'react-router-dom';import'./App.css';import CounterFeature from'./features/Counter';functionApp(){return(<div className="app"><Switch><Redirect from="home" to="/" exact /><Route path="/login" component={LoginFeature}/></Switch></div>);}exportdefault App;
Form Control
InputField
Chỉnh sửa file index.jsx trong thư mục InputField
import{ TextField }from'@material-ui/core';import PropTypes from'prop-types';import React from'react';import{ Controller }from"react-hook-form";import{ FormHelperText }from'@material-ui/core';
InputField.propTypes ={
form: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
label: PropTypes.string,
disabled: PropTypes.bool,};functionInputField(props){const{ form, name, label, disabled }= props
const{ formState:{ errors }}= form
const hasError = errors[name]return(<div><Controller
control={form.control}
name={name}
render={({ field })=>(<TextField
{...field}
fullWidth
margin="normal"
variant="outlined"
label={label}
disabled={disabled}
error={!!hasError}/>)}/><FormHelperText error={!!hasError}>{errors[name]?.message}</FormHelperText></div>);}exportdefault InputField;
PasswordField
Chỉnh sửa file index.jsx trong thư mục PasswordField
import{ FormHelperText }from'@material-ui/core';import FormControl from'@material-ui/core/FormControl';import IconButton from'@material-ui/core/IconButton';import InputAdornment from'@material-ui/core/InputAdornment';import InputLabel from'@material-ui/core/InputLabel';import OutlinedInput from'@material-ui/core/OutlinedInput';import Visibility from'@material-ui/icons/Visibility';import VisibilityOff from'@material-ui/icons/VisibilityOff';import React,{ useState }from'react';import{ Controller }from"react-hook-form";
PasswordField.propTypes ={};functionPasswordField(props){const{ form, name, label, disabled }= props
const{ formState:{ errors }}= form
const hasError = errors[name]const[showPassword, setShowPassword]=useState(false)consttoggleShowPassword=()=>{setShowPassword(!showPassword)}return(<div><FormControl error={!!hasError} variant="outlined" margin="normal" fullWidth><InputLabel htmlFor={name}>{label}</InputLabel><Controller
control={form.control}
name={name}
render={({ field })=>(<OutlinedInput
{...field}
id={name}
type={showPassword ?'text':'password'}
label={label}
endAdornment={<InputAdornment position="end"><IconButton
aria-label="toggle password visibility"
onClick={toggleShowPassword}
edge="end">{showPassword ?<Visibility />:<VisibilityOff />}</IconButton></InputAdornment>}
disabled={disabled}
error={!!hasError}
helperText={errors[name]?.message}
labelWidth={70}/>)}/><FormHelperText>{errors[name]?.message}</FormHelperText></FormControl></div>);}exportdefault PasswordField;
Constants
Chỉnh sửa file storage-keys.js
const StorageKeys ={
user:'user',
access:'access_token',
refresh:'refresh_token',}exportdefault StorageKeys
Api
axiosClient
Bạn tạo một phiên bản axios mới với cấu hình tùy chỉnh bằng cách chỉnh sửa file axiosClient.js trong thư mục api.
import axios from'axios';const axiosClient = axios.create({
baseURL:'http://127.0.0.1:8000/',
headers:{'content-type':'application/json',}})
userApi
import StorageKeys from"../constants/storage-keys";import axiosClient from"./axiosClient";const userApi ={register(data){const url ='register/';return axiosClient.post(url, data);},login(data){const url ='/api/token/';return axiosClient.post(url, data);},asyncgetUser(params){const newParams ={...params }const accessToken = localStorage.getItem(StorageKeys.access)const url =`users/`;const response =await axiosClient.get(url,{
params:{...newParams },
headers:{
Authorization:`Bearer ${accessToken}`}});return response
},asyncgetProfile(params){const newParams ={...params }const accessToken = localStorage.getItem(StorageKeys.access)const response =await axiosClient.get(`/detail/`,{
params:{...newParams },
headers:{
Authorization:`Bearer ${accessToken}`}})return response
},}exportdefault userApi
Auth
Tạo một slice user state cho Redux
Chỉnh sửa file userSlice.js trong thư mục Auth
import{ createAsyncThunk, createSlice }from'@reduxjs/toolkit';import userApi from'../../api/userApi';import StorageKeys from'../../constants/storage-keys';// createAsyncThunk cái này sử dụng cho login và registerexportconst register =createAsyncThunk('users/register',async(payload)=>{//call api to registerreturn data;})// createAsyncThunk cái này sử dụng cho login và registerexportconst login =createAsyncThunk('users/login',async(payload)=>{try{const response =await authApi.login(payload);
localStorage.setItem(StorageKeys.access, response.data.access);
localStorage.setItem(StorageKeys.refresh, response.data.refresh);const username =JSON.parse(response.config.data).username
const responseUser =await authApi.getUser({ username: username })const user ={...responseUser.data[0]}const responseProfile =await authApi.getProfile({user: user.id})const profile ={...responseProfile.data}const data ={...user,...profile,}
localStorage.setItem(StorageKeys.user,JSON.stringify(data));return data
}catch(error){
console.log(error)return error.message;}})const userSlice =createSlice({
name:'user',
initialState:{
current:JSON.parse(localStorage.getItem(StorageKeys.USER))||{},
settings:{},},
reducers:{logout(state){//clear local storage
state.current ={}
localStorage.removeItem(StorageKeys.access)
localStorage.removeItem(StorageKeys.refresh)
localStorage.removeItem(StorageKeys.user)}},
extraReducers:{[register.fulfilled]:(state, action)=>{
state.current = action.payload;},[login.fulfilled]:(state, action)=>{
state.current = action.payload;}}})const{ actions, reducer }= userSlice
exportconst{logout}= actions
exportdefault reducer
Store
Tạo một Redux Store
Tạo một Redux Store bằng cách chỉnh sửa file store.js trong thư mục store
import userReducer from'../features/Auth/userSlice'const{ configureStore }=require("@reduxjs/toolkit");const rootReducer ={
user: userReducer,}const store =configureStore({
reducer: rootReducer,})exportdefault store
Login
Chỉnh sửa file index.jsx trong thư mục Auth
import React from'react';import{ Route, Switch, useRouteMatch }from'react-router-dom';import LoginPage from'./page/LoginPage';import{ Box }from'@material-ui/core';
LoginFeature.propTypes ={};functionLoginFeature(props){const match =useRouteMatch()return(<div><Box pt={4}><Switch><Route path={match.url} component={LoginPage} exact /></Switch></Box></div>);}exportdefault LoginFeature;
Chỉnh sửa file LoginPage.jsx trong thư mục Auth/page
import{ makeStyles }from'@material-ui/core';import React from'react';import{ useSelector }from'react-redux';import LoginForm from'../components/LoginForm';const useStyles =makeStyles((theme)=>({
root:{
padding: theme.spacing(4,50),},}))
LoginPage.propTypes ={};functionLoginPage(props){const classes =useStyles();const loginInUser =useSelector(state=> state.user.current)const isLoggedIn =!!loginInUser.id
return(<div className={classes.root}>{!isLoggedIn &&(<><LoginForm /></>)}{isLoggedIn &&(<div><h2>Is Login</h2></div>)}</div>);}exportdefault LoginPage;
Chỉnh sửa file Login.jsx
import{ unwrapResult }from'@reduxjs/toolkit';import PropTypes from'prop-types';import React from'react';import{ useDispatch }from'react-redux';import{ login }from'../../userSlice';import LoginForm from'../LoginForm';
Login.propTypes ={};functionLogin(props){const dispatch =useDispatch()consthandleSubmit=async(values)=>{try{const actions =login(values)awaitdispatch(actions)}catch(error){
console.log(error)}}return(<div><LoginForm onSubmit={handleSubmit}/></div>);}exportdefault Login;
Chỉnh sửa file LoginForm.jsx
import{ yupResolver }from'@hookform/resolvers/yup';import{ Avatar, Button, LinearProgress, makeStyles, Typography }from'@material-ui/core';import LockOutlined from'@material-ui/icons/LockOutlined';import PropTypes from'prop-types';import React from'react';import{ useForm }from'react-hook-form';import*as yup from"yup";import InputField from'../../../../components/form-control/InputField';import PasswordField from'../../../../components/form-control/PasswordField';
LoginForm.propTypes ={
onSubmit: PropTypes.func,};const useStyles =makeStyles((theme)=>({
root:{
padding: theme.spacing(2,2),},
avatar:{
margin:"0 auto 15px",
backgroundColor: theme.palette.secondary.main,},
title:{
textAlign:"center",},
submit:{
margin: theme.spacing(2,0,0,0),},
linearProgress:{
margin: theme.spacing(0,0,4,0)}}))functionLoginForm(props){const classes =useStyles();const{ onSubmit }= props
const schema = yup.object().shape({
identifier: yup.string().required("Please enter your email.").email("Please enter a valid email"),
password: yup.string().required("Please enter your password.")});const form =useForm({
defaultValues:{
identifier:'',
password:'',},
resolver:yupResolver(schema),})consthandleSubmit=async(values)=>{if(onSubmit){awaitonSubmit(values)}}const{ isSubmitting }= form.formState
return(<div className={classes.root}>{isSubmitting &&<LinearProgress className={classes.linearProgress} color="secondary"/>}<Avatar className={classes.avatar}><LockOutlined /></Avatar><Typography component="h3" variant="h5" className={classes.title}>
Sign In
</Typography><form onSubmit={form.handleSubmit(handleSubmit)}><InputField name="identifier" label="Email" form={form}/><PasswordField name="password" label="Password" form={form}/><Button disabled={isSubmitting} type="submit" variant="contained" color="primary" fullWidth className={classes.submit}>
Sign in</Button></form></div>);}exportdefault LoginForm;
Bài viết đến đây là kết thúc. Chúc các bạn thành công
Nguồn: viblo.asia