I'm making a simple form to edit an app, the initial state of the name & description of the app is set using the data returned from the API.
Currently, when submitting the form the initial data seems to be logging as undefined, the name & description is being set as undefined which occurs in the first render (I have commented in the code where the logs are)
How can I make sure the initial state of name & description has the most up to date information? Is the excessive renders the problem?
Thanks for taking a look, any help would be appreciated.
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>
);
}
CodePudding user response:
When using const [name, setName] = useState(defaultName)
, if the defaultName
is updated in a future render, then the name
value will not be updated to the this latest value.
So in your case you can make the following changes :
const [name, setName] = useState();
const [description, setDescription] = useState();
useEffect(() => {
setName(selectedApp.name)
setDescription(selectedApp.description)
}, [selectedApp])
)
CodePudding user response:
- Name and Description are undefined
Your selectedApp is initialized as an empty object. Your useEffect fires a request off to retrieve that data, but the page renders once before it gets the response. There are a couple of ways to handle this. You could do anything from displaying a loading icon on the field, to having a default value for the field until your useEffect with [selectedApp] is called. Once that information is retrieved and sent back, your information will be up to date in state, but if you need to store it for later, you'll need to build out a function to save that data.
Default value:
const [name, setName] = useState(selectedApp.name ?? "Your default value here");
const [description, setDescription] = useState(selectedApp.description ?? "Your default value here");
Loading icon:
{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/>}