如何使用从 API 返回的最新数据设置初始状态?
How to set initial state with most up to date data returned from API?
我正在制作一个简单的表单来编辑应用程序,应用程序的名称和描述的初始状态是使用从 API.
返回的数据设置的
目前,当提交表单时,初始数据似乎被记录为未定义,名称和描述被设置为未定义,这发生在第一次渲染中(我在日志所在的代码中进行了评论)
如何确保名称和描述的初始状态具有最新信息?
是渲染过度的问题吗?
感谢您的浏览,如有任何帮助,我们将不胜感激。
import React, { useState, useContext, useEffect } from "react";
import Typography from "@material-ui/core/Typography";
import Button from "@material-ui/core/Button";
import Container from "@material-ui/core/Container";
import SaveIcon from "@mui/icons-material/Save";
import CloseIcon from "@mui/icons-material/Close";
import { makeStyles } from "@material-ui/styles";
import TextField from "@material-ui/core/TextField";
import { Grid } from "@mui/material";
import { useDispatch } from "react-redux";
import { updateApp, updateSelectedApp } from "../../services/reducers/apps";
import { EndpointContext } from "../../baxios/EndpointProvider";
import { useParams } from "react-router-dom";
export default function EditApp() {
const { appid } = useParams();
const classes = useStyles();
const dispatch = useDispatch();
const endpoints = useContext(EndpointContext);
const [selectedApp, setSelectedApp] = useState({});
const [isLoaded, setIsLoaded] = useState(false); // <--- Is there anyway I can also remove this useState? without this the default values in the forms dont populate
useEffect(() => {
async function fetchApp() {
await endpoints.appEndpoints.get(appid).then((response) => {
if (response.status === 200) {
setSelectedApp(response.data);
setIsLoaded(true);
}
});
}
fetchApp();
}, []);
useEffect(() => {
console.log(selectedApp);
}, [selectedApp]);
const [name, setName] = useState(selectedApp.name);
const [description, setDescription] = useState(selectedApp.description);
console.log("---", name, selectedApp.name); // <--- The page renders 3 times, each render log looks like this
// 1st render - --- undefined, undefined
// 2nd render - --- undefined, Appname
// 3rd render - --- undefined, Appname
const handleSubmit = (e) => {
e.preventDefault();
console.log("triggered", name, description); // <--- This logs (triggered, undefined, undefined)
if (name && description) {
const body = { name: name, description: description };
endpoints.appEndpoints.put(selectedApp.id, body).then((response) => {
if (response.status === 200) {
dispatch(updateApp(response.data));
setSelectedApp(response.data);
setName(selectedApp.name);
setDescription(selectedApp.description);
}
});
}
};
return (
<div style={{ margin: 100, marginLeft: 350 }}>
{isLoaded ? (
<Container size="sm" style={{ marginTop: 40 }}>
<Typography
variant="h6"
color="textSecondary"
component="h2"
gutterBottom
>
Edit App
</Typography>
<form noValidate autoComplete="off" onSubmit={handleSubmit}>
<TextField
className={classes.field}
onChange={(e) => setName(e.target.value)}
label="App Name"
variant="outlined"
color="secondary"
fullWidth
required
size="small"
defaultValue={selectedApp.name}
error={nameError}
/>
<TextField
className={classes.field}
onChange={(e) => setDescription(e.target.value)}
label="Description"
variant="outlined"
color="secondary"
rows={4}
fullWidth
required
size="small"
defaultValue={selectedApp.description}
error={descriptionError}
/>
<Grid container spacing={2}>
<Grid item>
<Button
// onClick={handleSubmit}
type="submit"
color="primary"
variant="contained"
endIcon={<SaveIcon />}
>
Save
</Button>
</Grid>
</Grid>
</form>
</Container>
) : (
<></>
)}
</div>
);
}
使用 const [name, setName] = useState(defaultName)
时,如果 defaultName
在未来的渲染中更新,则 name
值将 不会 更新为这个最新值。
因此,对于您的情况,您可以进行以下更改:
const [name, setName] = useState();
const [description, setDescription] = useState();
useEffect(() => {
setName(selectedApp.name)
setDescription(selectedApp.description)
}, [selectedApp])
)
- 名称和描述未定义
您的 selectedApp 被初始化为一个空对象。您的 useEffect 触发请求以检索该数据,但页面在获得响应之前呈现一次。有几种方法可以处理这个问题。您可以做任何事情,从在字段上显示加载图标,到为字段设置默认值,直到调用 [selectedApp] 的 useEffect。检索并发回该信息后,您的信息将处于最新状态,但如果您需要存储它以备后用,则需要构建一个函数来保存该数据。
默认值:
const [name, setName] = useState(selectedApp.name ?? "Your default value here");
const [description, setDescription] = useState(selectedApp.description ?? "Your default value here");
加载图标:
{selectedApp ? (
<form noValidate autoComplete="off" onSubmit={handleSubmit}>
<TextField
className={classes.field}
onChange={(e) => setName(e.target.value)}
label="App Name"
variant="outlined"
color="secondary"
fullWidth
required
size="small"
defaultValue={selectedApp.name}
error={nameError}
/>
<TextField
className={classes.field}
onChange={(e) => setDescription(e.target.value)}
label="Description"
variant="outlined"
color="secondary"
rows={4}
fullWidth
required
size="small"
defaultValue={selectedApp.description}
error={descriptionError}
/>
<Grid container spacing={2}>
<Grid item>
<Button
// onClick={handleSubmit}
type="submit"
color="primary"
variant="contained"
endIcon={<SaveIcon />}
>
Save
</Button>
</Grid>
</Grid>
</form>
) : <LoadingIconComponent/>}
我正在制作一个简单的表单来编辑应用程序,应用程序的名称和描述的初始状态是使用从 API.
返回的数据设置的目前,当提交表单时,初始数据似乎被记录为未定义,名称和描述被设置为未定义,这发生在第一次渲染中(我在日志所在的代码中进行了评论)
如何确保名称和描述的初始状态具有最新信息? 是渲染过度的问题吗?
感谢您的浏览,如有任何帮助,我们将不胜感激。
import React, { useState, useContext, useEffect } from "react";
import Typography from "@material-ui/core/Typography";
import Button from "@material-ui/core/Button";
import Container from "@material-ui/core/Container";
import SaveIcon from "@mui/icons-material/Save";
import CloseIcon from "@mui/icons-material/Close";
import { makeStyles } from "@material-ui/styles";
import TextField from "@material-ui/core/TextField";
import { Grid } from "@mui/material";
import { useDispatch } from "react-redux";
import { updateApp, updateSelectedApp } from "../../services/reducers/apps";
import { EndpointContext } from "../../baxios/EndpointProvider";
import { useParams } from "react-router-dom";
export default function EditApp() {
const { appid } = useParams();
const classes = useStyles();
const dispatch = useDispatch();
const endpoints = useContext(EndpointContext);
const [selectedApp, setSelectedApp] = useState({});
const [isLoaded, setIsLoaded] = useState(false); // <--- Is there anyway I can also remove this useState? without this the default values in the forms dont populate
useEffect(() => {
async function fetchApp() {
await endpoints.appEndpoints.get(appid).then((response) => {
if (response.status === 200) {
setSelectedApp(response.data);
setIsLoaded(true);
}
});
}
fetchApp();
}, []);
useEffect(() => {
console.log(selectedApp);
}, [selectedApp]);
const [name, setName] = useState(selectedApp.name);
const [description, setDescription] = useState(selectedApp.description);
console.log("---", name, selectedApp.name); // <--- The page renders 3 times, each render log looks like this
// 1st render - --- undefined, undefined
// 2nd render - --- undefined, Appname
// 3rd render - --- undefined, Appname
const handleSubmit = (e) => {
e.preventDefault();
console.log("triggered", name, description); // <--- This logs (triggered, undefined, undefined)
if (name && description) {
const body = { name: name, description: description };
endpoints.appEndpoints.put(selectedApp.id, body).then((response) => {
if (response.status === 200) {
dispatch(updateApp(response.data));
setSelectedApp(response.data);
setName(selectedApp.name);
setDescription(selectedApp.description);
}
});
}
};
return (
<div style={{ margin: 100, marginLeft: 350 }}>
{isLoaded ? (
<Container size="sm" style={{ marginTop: 40 }}>
<Typography
variant="h6"
color="textSecondary"
component="h2"
gutterBottom
>
Edit App
</Typography>
<form noValidate autoComplete="off" onSubmit={handleSubmit}>
<TextField
className={classes.field}
onChange={(e) => setName(e.target.value)}
label="App Name"
variant="outlined"
color="secondary"
fullWidth
required
size="small"
defaultValue={selectedApp.name}
error={nameError}
/>
<TextField
className={classes.field}
onChange={(e) => setDescription(e.target.value)}
label="Description"
variant="outlined"
color="secondary"
rows={4}
fullWidth
required
size="small"
defaultValue={selectedApp.description}
error={descriptionError}
/>
<Grid container spacing={2}>
<Grid item>
<Button
// onClick={handleSubmit}
type="submit"
color="primary"
variant="contained"
endIcon={<SaveIcon />}
>
Save
</Button>
</Grid>
</Grid>
</form>
</Container>
) : (
<></>
)}
</div>
);
}
使用 const [name, setName] = useState(defaultName)
时,如果 defaultName
在未来的渲染中更新,则 name
值将 不会 更新为这个最新值。
因此,对于您的情况,您可以进行以下更改:
const [name, setName] = useState();
const [description, setDescription] = useState();
useEffect(() => {
setName(selectedApp.name)
setDescription(selectedApp.description)
}, [selectedApp])
)
- 名称和描述未定义
您的 selectedApp 被初始化为一个空对象。您的 useEffect 触发请求以检索该数据,但页面在获得响应之前呈现一次。有几种方法可以处理这个问题。您可以做任何事情,从在字段上显示加载图标,到为字段设置默认值,直到调用 [selectedApp] 的 useEffect。检索并发回该信息后,您的信息将处于最新状态,但如果您需要存储它以备后用,则需要构建一个函数来保存该数据。
默认值:
const [name, setName] = useState(selectedApp.name ?? "Your default value here");
const [description, setDescription] = useState(selectedApp.description ?? "Your default value here");
加载图标:
{selectedApp ? (
<form noValidate autoComplete="off" onSubmit={handleSubmit}>
<TextField
className={classes.field}
onChange={(e) => setName(e.target.value)}
label="App Name"
variant="outlined"
color="secondary"
fullWidth
required
size="small"
defaultValue={selectedApp.name}
error={nameError}
/>
<TextField
className={classes.field}
onChange={(e) => setDescription(e.target.value)}
label="Description"
variant="outlined"
color="secondary"
rows={4}
fullWidth
required
size="small"
defaultValue={selectedApp.description}
error={descriptionError}
/>
<Grid container spacing={2}>
<Grid item>
<Button
// onClick={handleSubmit}
type="submit"
color="primary"
variant="contained"
endIcon={<SaveIcon />}
>
Save
</Button>
</Grid>
</Grid>
</form>
) : <LoadingIconComponent/>}