useFormik 导致太多重新渲染
useFormik cause too many re-renders
堆栈:
- 下一个TS
- 福米克
- Redux 工具包
- MUI5
- 是的
我有一个用户页面,显示用户列表。此页面也有删除用户和编辑用户操作。编辑用户,将所选用户保存在 Redux 状态,并打开一个带有新组件 UserDetail 的 MUI5 SwipeableDrawer。在 UserDetail 页面中,使用 useState 获取 selectedUser 并将其设置为使用 Formik 增强的表单。但它因“重新渲染太多”错误而失败。
相关文件如下:
用户组件:
import React, {useState} from 'react';
import {
Alert,
Box,
Button,
ButtonGroup,
CircularProgress,
Container,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
Snackbar,
SwipeableDrawer,
Table,
TableBody,
TableCell,
TableContainer,
TableFooter,
TableHead,
TableRow
} from "@mui/material";
import moment from "moment";
import {Delete, Edit, PersonAdd} from "@mui/icons-material";
import {useAppDispatch} from 'services/hooks';
import {useRouter} from "next/router";
import {NextPage} from "next";
import {useDeleteUserMutation, useGetUsersQuery} from "../../services/UserService";
import Footer from "../../components/Footer/Footer";
import UserDetail from "./components/UserDetail";
import {UserType} from "../../services/types/UserType";
import {setUser} from "../../services/slices/UserSlice";
const EMPTY_DIALOG = {
open: false,
text: '',
title: '',
onConfirm: () => {
},
onCancel: () => {
}
}
const EMPTY_ALERT = {
open: false,
text: '',
};
const Users: NextPage = () => {
console.log('users')
const router = useRouter();
const dispatch = useAppDispatch();
const [offset, setOffset] = useState(0);
const [limit, setLimit] = useState(10);
const [dialog, setDialog] = useState(EMPTY_DIALOG);
const [alert, setAlert] = useState(EMPTY_ALERT);
const {
data,
error,
isLoading: isUsersLoading,
isSuccess: isUsersQueried,
isFetching: isUsersFetching,
isError: isUsersError
} = useGetUsersQuery();
const [deleteUser, {
data: deletedUser,
isLoading: isUserDeleting,
isSuccess: isUserDeleted
}] = useDeleteUserMutation();
const drawerBleeding = 56;
const [openDrawer, setOpenDrawer] = React.useState(false);
const handleDeleteUser = (userId: number) => async () => {
try {
await deleteUser(userId).unwrap();
setAlert({
open: true,
text: `Successfully deleted user: ${userId}`,
});
resetDeleteDialog();
} catch (error) {
console.log(`Error: Failed deleting user with id ${userId}`);
}
};
const resetDeleteDialog = () => {
setDialog(EMPTY_DIALOG);
}
const openDeleteDialog = (userId: number) => () => {
setDialog({
open: true,
title: 'Delete user',
text: `Delete user: ${userId}?`,
onConfirm: handleDeleteUser(userId),
onCancel: () => resetDeleteDialog()
});
}
const resetAlert = () => {
setAlert(EMPTY_ALERT);
}
const editUser = (user: UserType) => () => {
setOpenDrawer(true);
dispatch(setUser(user));
};
const toggleEditDrawer = (newOpen: boolean) => () => {
setOpenDrawer(newOpen);
};
const renderTable = (users: UserType[], count: number) => {
const hasUsers = count > 0;
return (
<React.Fragment>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell colSpan={6} align="right">
<Button variant="outlined" color="primary" onClick={toggleEditDrawer(true)}>
<PersonAdd/>
</Button>
</TableCell>
</TableRow>
<TableRow>
<TableCell>Id</TableCell>
<TableCell>First name</TableCell>
<TableCell>Last name</TableCell>
<TableCell>Email</TableCell>
<TableCell>Birth date</TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{hasUsers ? (
users.map((user) => (
<TableRow key={user.id}>
<TableCell>{user.id}</TableCell>
<TableCell>{user.firstName}</TableCell>
<TableCell>{user.lastName}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>
{moment.utc(user.birthDate).format('MM-DD-YYYY')}
</TableCell>
<TableCell sx={{textAlign: "right"}}>
<ButtonGroup>
<Button onClick={editUser(user)}>
<Edit/>
</Button>
<Button onClick={openDeleteDialog(user.id)}>
{<Delete/>}
</Button>
</ButtonGroup>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={6}>No users found.</TableCell>
</TableRow>
)}
</TableBody>
<TableFooter>
<TableRow>
{/*<TablePagination*/}
{/* component={TableCell}*/}
{/* count={count}*/}
{/* page={offset}*/}
{/* rowsPerPage={limit}*/}
{/* onChangePage={handleChangePage}*/}
{/* onChangeRowsPerPage={handleChangeRowsPerPage}*/}
{/*/>*/}
</TableRow>
</TableFooter>
</Table>
</TableContainer>
<SwipeableDrawer
anchor="bottom"
open={openDrawer}
onClose={toggleEditDrawer(false)}
onOpen={toggleEditDrawer(true)}
swipeAreaWidth={drawerBleeding}
disableSwipeToOpen={false}
ModalProps={{
keepMounted: true,
}}
>
<UserDetail toggleEditDrawer={toggleEditDrawer}></UserDetail>
</SwipeableDrawer>
</React.Fragment>
);
}
const renderBody = () => {
if (isUsersQueried) {
const {users, count} = data;
return (isUsersFetching || isUsersLoading) ?
<Box sx={{display: 'flex'}}>
<CircularProgress/>
</Box> :
renderTable(users, count)
}
}
const renderError = () => {
return isUsersError && <Alert severity="error">{error}</Alert>;
}
return (
<Container maxWidth={"md"} fixed>
{renderError()}
{renderBody()}
<Footer></Footer>
<Dialog
open={dialog.open}
onClose={dialog.onCancel}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{dialog.title}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{dialog.text}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={dialog.onCancel}>Disagree</Button>
<Button onClick={dialog.onConfirm} autoFocus>
Agree
</Button>
</DialogActions>
</Dialog>
<Snackbar
open={alert.open}
autoHideDuration={6000}
onClose={resetAlert}
message={alert.text}
/>
</Container>
);
}
export default Users;
UserDetail 组件:
import React, {useState} from 'react';
import {Alert, Box, Button, Container, Grid, TextField, Typography} from "@mui/material";
import {useSelector} from "react-redux";
import * as yup from 'yup';
import {useFormik} from "formik";
import AdapterMoment from '@mui/lab/AdapterMoment';
import LocalizationProvider from '@mui/lab/LocalizationProvider';
import {DatePicker} from "@mui/lab";
import {NextPage} from "next";
import Footer from "../../../components/Footer/Footer";
import {useCreateUserMutation, useUpdateUserMutation} from "../../../services/UserService";
import {UserType} from "../../../services/types/UserType";
import {AppProps} from "next/app";
import {selectUser} from "../../../services/slices/UserSlice";
const validationSchema = yup.object({
email: yup
.string()
.trim()
.email('Please enter a valid email address')
.required('Email is required.'),
firstName: yup
.string()
.required('Please specify your first name'),
lastName: yup
.string()
.required('Please specify your first name'),
birthDate: yup
.date()
});
const INITIAL_USER = {
firstName: '',
lastName: '',
email: ''
}
const UserDetail: NextPage = ({toggleEditDrawer}: AppProps) => {
console.log('user detail')
const [birthDate, setBirthDate] = useState(null);
const [pageError, setPageError] = useState(null);
const user = useSelector(selectUser);
const [createUser, {
isLoading: isUserCreating,
isSuccess: isUserCreated
}] = useCreateUserMutation();
// you can get the detailed user if really needed
// const {
// data: user,
// isLoading: isUserLoading
// } = useGetUserQuery(user.id);
const [updateUser, {isLoading: isUserUpdating}] = useUpdateUserMutation();
const onSubmit = (values: UserType) => {
let newValues = {
...values,
birthDate: birthDate.toISOString()
}
try {
if (user && user.id) {
newValues.id = user.id;
updateUser(newValues).unwrap();
} else {
createUser(newValues).unwrap();
}
} catch (error) {
setPageError(error);
} finally {
toggleEditDrawer(false)();
}
}
const formik = useFormik({
initialValues: INITIAL_USER,
validationSchema: validationSchema,
onSubmit
});
const renderForm = () => {
console.log(user.birthDate)
setBirthDate(moment(user.birthDate));
// this part of the code causes the too many re-render error
formik.setValues({
firstName: user.firstName,
lastName: user.lastName,
email: user.email
});
return (
<form onSubmit={formik.handleSubmit}>
<Grid container spacing={4}>
<Grid item xs={12}>
<Typography variant={'subtitle2'} sx={{marginBottom: 2}}>
Enter your email
</Typography>
<TextField
label="Email *"
variant="outlined"
name={'email'}
fullWidth
value={formik.values.email}
onChange={formik.handleChange}
error={formik.touched.email && Boolean(formik.errors.email)}
helperText={formik.touched.email && formik.errors.email}
/>
</Grid>
<Grid item xs={12}>
<Typography variant={'subtitle2'} sx={{marginBottom: 2}}>
Enter your firstname
</Typography>
<TextField
label="Firstname *"
variant="outlined"
name={'firstName'}
fullWidth
value={formik.values.firstName}
onChange={formik.handleChange}
error={formik.touched.firstName && Boolean(formik.errors.firstName)}
helperText={formik.touched.firstName && formik.errors.firstName}
/>
</Grid>
<Grid item xs={12}>
<Typography variant={'subtitle2'} sx={{marginBottom: 2}}>
Enter your lastName
</Typography>
<TextField
label="Lastname *"
variant="outlined"
name={'lastName'}
fullWidth
value={formik.values.lastName}
onChange={formik.handleChange}
error={formik.touched.lastName && Boolean(formik.errors.lastName)}
helperText={formik.touched.lastName && formik.errors.lastName}
/>
</Grid>
<Grid item xs={12}>
<Typography variant={'subtitle2'} sx={{marginBottom: 2}}>
Enter your birthdate
</Typography>
<LocalizationProvider dateAdapter={AdapterMoment}>
<DatePicker
label="Birthdate"
value={birthDate}
onChange={(newValue) => {
setBirthDate(newValue);
}}
renderInput={(params) => <TextField {...params} variant={"outlined"} fullWidth required/>}
/>
</LocalizationProvider>
</Grid>
<Grid item xs={12}>
<Typography variant="subtitle2" gutterBottom>
Fields that are marked with * sign are required.
</Typography>
<Grid container spacing={2}>
<Grid item>
<Button
size="large"
variant="contained"
color="primary"
type={"submit"}
>
Save
</Button>
</Grid>
<Grid item>
<Button size="large" variant="contained" color="secondary" onClick={toggleEditDrawer(false)}>
Cancel
</Button>
</Grid>
</Grid>
</Grid>
</Grid>
</form>
);
}
return (
<Container maxWidth={"md"}>
<Box sx={{margin: 2}}>
{pageError && <Alert severity="error">{pageError}</Alert>}
<Box marginBottom={4}>
<Typography
sx={{
textTransform: 'uppercase',
fontWeight: 'medium',
}}
gutterBottom
color={'text.secondary'}
>
Create User
</Typography>
<Typography color="text.secondary">
Enter the details
</Typography>
</Box>
</Box>
{user && renderForm()}
<Footer></Footer>
</Container>
);
}
export default UserDetail;
代码formik.setValues导致重新渲染错误太多。知道如何解决吗?谢谢
你永远不应该在渲染过程中调用诸如 formik.setValues
之类的副作用。这将导致另一个渲染,然后再次调用它。
相反,在 useEffect
.
中做这样的事情
所以,例如
useEffect(() => {
formik.setValues({
firstName: user.firstName,
lastName: user.lastName,
email: user.email
});
}, [user])
每次 user
更改时都会调用 setValues
,但不会超出此范围。
@phry 在这种情况下,我们不需要将 formik conts 放在 useEffect 依赖项下吗?如果是这样,它会导致同样的问题,还是渲染循环?
另一个修复方法是将表单用作组件并在其中初始化 formik(这相当于您当前的 renderForm
函数:
const Page: NextPage = () => {
const user = selectUser
{...}
// You can extract this component also outside to the current module
const Form = ({onSubmit, user: {name, email}}) => (
const formik = useFormik({
initialValues: {
name,
email
},
validationSchema: validationSchema,
onSubmit
});
return (
<form>{...}</form>
)
)
return (
{...}
{user && <Form user={user} onSubmit={onSubmit} />}
)
}
堆栈:
- 下一个TS
- 福米克
- Redux 工具包
- MUI5
- 是的
我有一个用户页面,显示用户列表。此页面也有删除用户和编辑用户操作。编辑用户,将所选用户保存在 Redux 状态,并打开一个带有新组件 UserDetail 的 MUI5 SwipeableDrawer。在 UserDetail 页面中,使用 useState 获取 selectedUser 并将其设置为使用 Formik 增强的表单。但它因“重新渲染太多”错误而失败。
相关文件如下:
用户组件:
import React, {useState} from 'react';
import {
Alert,
Box,
Button,
ButtonGroup,
CircularProgress,
Container,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
Snackbar,
SwipeableDrawer,
Table,
TableBody,
TableCell,
TableContainer,
TableFooter,
TableHead,
TableRow
} from "@mui/material";
import moment from "moment";
import {Delete, Edit, PersonAdd} from "@mui/icons-material";
import {useAppDispatch} from 'services/hooks';
import {useRouter} from "next/router";
import {NextPage} from "next";
import {useDeleteUserMutation, useGetUsersQuery} from "../../services/UserService";
import Footer from "../../components/Footer/Footer";
import UserDetail from "./components/UserDetail";
import {UserType} from "../../services/types/UserType";
import {setUser} from "../../services/slices/UserSlice";
const EMPTY_DIALOG = {
open: false,
text: '',
title: '',
onConfirm: () => {
},
onCancel: () => {
}
}
const EMPTY_ALERT = {
open: false,
text: '',
};
const Users: NextPage = () => {
console.log('users')
const router = useRouter();
const dispatch = useAppDispatch();
const [offset, setOffset] = useState(0);
const [limit, setLimit] = useState(10);
const [dialog, setDialog] = useState(EMPTY_DIALOG);
const [alert, setAlert] = useState(EMPTY_ALERT);
const {
data,
error,
isLoading: isUsersLoading,
isSuccess: isUsersQueried,
isFetching: isUsersFetching,
isError: isUsersError
} = useGetUsersQuery();
const [deleteUser, {
data: deletedUser,
isLoading: isUserDeleting,
isSuccess: isUserDeleted
}] = useDeleteUserMutation();
const drawerBleeding = 56;
const [openDrawer, setOpenDrawer] = React.useState(false);
const handleDeleteUser = (userId: number) => async () => {
try {
await deleteUser(userId).unwrap();
setAlert({
open: true,
text: `Successfully deleted user: ${userId}`,
});
resetDeleteDialog();
} catch (error) {
console.log(`Error: Failed deleting user with id ${userId}`);
}
};
const resetDeleteDialog = () => {
setDialog(EMPTY_DIALOG);
}
const openDeleteDialog = (userId: number) => () => {
setDialog({
open: true,
title: 'Delete user',
text: `Delete user: ${userId}?`,
onConfirm: handleDeleteUser(userId),
onCancel: () => resetDeleteDialog()
});
}
const resetAlert = () => {
setAlert(EMPTY_ALERT);
}
const editUser = (user: UserType) => () => {
setOpenDrawer(true);
dispatch(setUser(user));
};
const toggleEditDrawer = (newOpen: boolean) => () => {
setOpenDrawer(newOpen);
};
const renderTable = (users: UserType[], count: number) => {
const hasUsers = count > 0;
return (
<React.Fragment>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell colSpan={6} align="right">
<Button variant="outlined" color="primary" onClick={toggleEditDrawer(true)}>
<PersonAdd/>
</Button>
</TableCell>
</TableRow>
<TableRow>
<TableCell>Id</TableCell>
<TableCell>First name</TableCell>
<TableCell>Last name</TableCell>
<TableCell>Email</TableCell>
<TableCell>Birth date</TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{hasUsers ? (
users.map((user) => (
<TableRow key={user.id}>
<TableCell>{user.id}</TableCell>
<TableCell>{user.firstName}</TableCell>
<TableCell>{user.lastName}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>
{moment.utc(user.birthDate).format('MM-DD-YYYY')}
</TableCell>
<TableCell sx={{textAlign: "right"}}>
<ButtonGroup>
<Button onClick={editUser(user)}>
<Edit/>
</Button>
<Button onClick={openDeleteDialog(user.id)}>
{<Delete/>}
</Button>
</ButtonGroup>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={6}>No users found.</TableCell>
</TableRow>
)}
</TableBody>
<TableFooter>
<TableRow>
{/*<TablePagination*/}
{/* component={TableCell}*/}
{/* count={count}*/}
{/* page={offset}*/}
{/* rowsPerPage={limit}*/}
{/* onChangePage={handleChangePage}*/}
{/* onChangeRowsPerPage={handleChangeRowsPerPage}*/}
{/*/>*/}
</TableRow>
</TableFooter>
</Table>
</TableContainer>
<SwipeableDrawer
anchor="bottom"
open={openDrawer}
onClose={toggleEditDrawer(false)}
onOpen={toggleEditDrawer(true)}
swipeAreaWidth={drawerBleeding}
disableSwipeToOpen={false}
ModalProps={{
keepMounted: true,
}}
>
<UserDetail toggleEditDrawer={toggleEditDrawer}></UserDetail>
</SwipeableDrawer>
</React.Fragment>
);
}
const renderBody = () => {
if (isUsersQueried) {
const {users, count} = data;
return (isUsersFetching || isUsersLoading) ?
<Box sx={{display: 'flex'}}>
<CircularProgress/>
</Box> :
renderTable(users, count)
}
}
const renderError = () => {
return isUsersError && <Alert severity="error">{error}</Alert>;
}
return (
<Container maxWidth={"md"} fixed>
{renderError()}
{renderBody()}
<Footer></Footer>
<Dialog
open={dialog.open}
onClose={dialog.onCancel}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{dialog.title}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{dialog.text}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={dialog.onCancel}>Disagree</Button>
<Button onClick={dialog.onConfirm} autoFocus>
Agree
</Button>
</DialogActions>
</Dialog>
<Snackbar
open={alert.open}
autoHideDuration={6000}
onClose={resetAlert}
message={alert.text}
/>
</Container>
);
}
export default Users;
UserDetail 组件:
import React, {useState} from 'react';
import {Alert, Box, Button, Container, Grid, TextField, Typography} from "@mui/material";
import {useSelector} from "react-redux";
import * as yup from 'yup';
import {useFormik} from "formik";
import AdapterMoment from '@mui/lab/AdapterMoment';
import LocalizationProvider from '@mui/lab/LocalizationProvider';
import {DatePicker} from "@mui/lab";
import {NextPage} from "next";
import Footer from "../../../components/Footer/Footer";
import {useCreateUserMutation, useUpdateUserMutation} from "../../../services/UserService";
import {UserType} from "../../../services/types/UserType";
import {AppProps} from "next/app";
import {selectUser} from "../../../services/slices/UserSlice";
const validationSchema = yup.object({
email: yup
.string()
.trim()
.email('Please enter a valid email address')
.required('Email is required.'),
firstName: yup
.string()
.required('Please specify your first name'),
lastName: yup
.string()
.required('Please specify your first name'),
birthDate: yup
.date()
});
const INITIAL_USER = {
firstName: '',
lastName: '',
email: ''
}
const UserDetail: NextPage = ({toggleEditDrawer}: AppProps) => {
console.log('user detail')
const [birthDate, setBirthDate] = useState(null);
const [pageError, setPageError] = useState(null);
const user = useSelector(selectUser);
const [createUser, {
isLoading: isUserCreating,
isSuccess: isUserCreated
}] = useCreateUserMutation();
// you can get the detailed user if really needed
// const {
// data: user,
// isLoading: isUserLoading
// } = useGetUserQuery(user.id);
const [updateUser, {isLoading: isUserUpdating}] = useUpdateUserMutation();
const onSubmit = (values: UserType) => {
let newValues = {
...values,
birthDate: birthDate.toISOString()
}
try {
if (user && user.id) {
newValues.id = user.id;
updateUser(newValues).unwrap();
} else {
createUser(newValues).unwrap();
}
} catch (error) {
setPageError(error);
} finally {
toggleEditDrawer(false)();
}
}
const formik = useFormik({
initialValues: INITIAL_USER,
validationSchema: validationSchema,
onSubmit
});
const renderForm = () => {
console.log(user.birthDate)
setBirthDate(moment(user.birthDate));
// this part of the code causes the too many re-render error
formik.setValues({
firstName: user.firstName,
lastName: user.lastName,
email: user.email
});
return (
<form onSubmit={formik.handleSubmit}>
<Grid container spacing={4}>
<Grid item xs={12}>
<Typography variant={'subtitle2'} sx={{marginBottom: 2}}>
Enter your email
</Typography>
<TextField
label="Email *"
variant="outlined"
name={'email'}
fullWidth
value={formik.values.email}
onChange={formik.handleChange}
error={formik.touched.email && Boolean(formik.errors.email)}
helperText={formik.touched.email && formik.errors.email}
/>
</Grid>
<Grid item xs={12}>
<Typography variant={'subtitle2'} sx={{marginBottom: 2}}>
Enter your firstname
</Typography>
<TextField
label="Firstname *"
variant="outlined"
name={'firstName'}
fullWidth
value={formik.values.firstName}
onChange={formik.handleChange}
error={formik.touched.firstName && Boolean(formik.errors.firstName)}
helperText={formik.touched.firstName && formik.errors.firstName}
/>
</Grid>
<Grid item xs={12}>
<Typography variant={'subtitle2'} sx={{marginBottom: 2}}>
Enter your lastName
</Typography>
<TextField
label="Lastname *"
variant="outlined"
name={'lastName'}
fullWidth
value={formik.values.lastName}
onChange={formik.handleChange}
error={formik.touched.lastName && Boolean(formik.errors.lastName)}
helperText={formik.touched.lastName && formik.errors.lastName}
/>
</Grid>
<Grid item xs={12}>
<Typography variant={'subtitle2'} sx={{marginBottom: 2}}>
Enter your birthdate
</Typography>
<LocalizationProvider dateAdapter={AdapterMoment}>
<DatePicker
label="Birthdate"
value={birthDate}
onChange={(newValue) => {
setBirthDate(newValue);
}}
renderInput={(params) => <TextField {...params} variant={"outlined"} fullWidth required/>}
/>
</LocalizationProvider>
</Grid>
<Grid item xs={12}>
<Typography variant="subtitle2" gutterBottom>
Fields that are marked with * sign are required.
</Typography>
<Grid container spacing={2}>
<Grid item>
<Button
size="large"
variant="contained"
color="primary"
type={"submit"}
>
Save
</Button>
</Grid>
<Grid item>
<Button size="large" variant="contained" color="secondary" onClick={toggleEditDrawer(false)}>
Cancel
</Button>
</Grid>
</Grid>
</Grid>
</Grid>
</form>
);
}
return (
<Container maxWidth={"md"}>
<Box sx={{margin: 2}}>
{pageError && <Alert severity="error">{pageError}</Alert>}
<Box marginBottom={4}>
<Typography
sx={{
textTransform: 'uppercase',
fontWeight: 'medium',
}}
gutterBottom
color={'text.secondary'}
>
Create User
</Typography>
<Typography color="text.secondary">
Enter the details
</Typography>
</Box>
</Box>
{user && renderForm()}
<Footer></Footer>
</Container>
);
}
export default UserDetail;
代码formik.setValues导致重新渲染错误太多。知道如何解决吗?谢谢
你永远不应该在渲染过程中调用诸如 formik.setValues
之类的副作用。这将导致另一个渲染,然后再次调用它。
相反,在 useEffect
.
所以,例如
useEffect(() => {
formik.setValues({
firstName: user.firstName,
lastName: user.lastName,
email: user.email
});
}, [user])
每次 user
更改时都会调用 setValues
,但不会超出此范围。
@phry 在这种情况下,我们不需要将 formik conts 放在 useEffect 依赖项下吗?如果是这样,它会导致同样的问题,还是渲染循环?
另一个修复方法是将表单用作组件并在其中初始化 formik(这相当于您当前的 renderForm
函数:
const Page: NextPage = () => {
const user = selectUser
{...}
// You can extract this component also outside to the current module
const Form = ({onSubmit, user: {name, email}}) => (
const formik = useFormik({
initialValues: {
name,
email
},
validationSchema: validationSchema,
onSubmit
});
return (
<form>{...}</form>
)
)
return (
{...}
{user && <Form user={user} onSubmit={onSubmit} />}
)
}