I am trying to implement a two-page form in React. On the first page I request a code that I use to fetch some data that I need to display on the second page. Here, by page I mean a different view on the same page. The problem that I am facing is that when I receive a response from the backend API, I use state to update the current state of the fields and this does not seem to happen immediately when I need it. When I change the value of the property that decides which view to render, the fields are still empty, not updating with the values fetched from the API. Now, I know that setState is asynchronous, but I don't know how to handle this situation, since the data fetch is done on demand, not at the beginning, so I don't think I can use useEffect for this job.
Here is my state:
const [state, setState] = useState({
name: "",
cif: "",
address: "",
phone: "",
fax: "",
managerName: "",
email: "",
password: "",
});
const [companyDataFetched, setCompanyDataFetched] = useState(false);
And here are the two views and how I switch between them:
{!companyDataFetched ? (
<CustomForm layout={theme.layout} widthelem={"70%"}>
<Form.Item
name="Company CIF:"
label="Company CIF:"
rules={[
{
pattern: new RegExp(/^\d{2,10}$/),
message: "Invalid CIF!",
},
{
required: true,
message: "Please insert the CIF of the company!",
},
]}
>
<CustomInput
backgroundcolor={theme.white}
onChange={(event) => handleChange(event, "cif")}
/>
</Form.Item>
<CustomButton
backgroundcolor={theme.primaryColor}
textcolor={theme.white}
onClick={() => {
axios.get(`${process.env.REACT_APP_API_URL}/Company/GetByCUI/${state.cif}`)
.then((res) => {
const companyInfo = res.data;
if (companyInfo.name) {
setState((prevState) => {
return {
...prevState,
name: companyInfo.name
};
});
}
if (companyInfo.address) {
setState((prevState) => {
return {
...prevState,
"address": companyInfo.address,
};
});
}
if (companyInfo.phone) {
setState((prevState) => {
return {
...prevState,
"phone": companyInfo.phone,
};
});
}
if (companyInfo.fax) {
setState((prevState) => {
return {
...prevState,
"fax": companyInfo.fax,
};
});
}
setCompanyDataFetched(true);
})
.catch((error) => {
console.log(error);
});
}}
margintop={"13%"}
marginbottom={"13%"}
>
Continue
</CustomButton>
</CustomForm>
) : (
<CustomForm layout={theme.layout} widthelem={"70%"}>
<Form.Item
name="Company name:"
label="Company name:"
rules={[
{
required: true,
message: "Please insert the name of the company!",
whitespace: true,
},
]}
>
<CustomInput
backgroundcolor={theme.white}
onChange={(event) => handleChange(event, "name")}
value={state.name}
/>
</Form.Item>
<Form.Item
name="Company CIF:"
label="Company CIF:"
rules={[
{
pattern: new RegExp(/^\d{2,10}$/),
message: "Invalid CIF!",
},
{
required: true,
message: "Please insert the CIF of the company!",
},
]}
>
<CustomInput
backgroundcolor={theme.white}
onChange={(event) => handleChange(event, "cif")}
value={state.cif}
disabled={true}
/>
</Form.Item>
<Form.Item
name="Address:"
label="Address:"
rules={[
{
required: true,
message: "Please insert the address of the company!",
whitespace: true,
},
]}
>
<CustomInput
backgroundcolor={theme.white}
onChange={(event) => handleChange(event, "address")}
value={state.address}
/>
</Form.Item>
<Form.Item
name="Phone:"
label="Phone:"
rules={[
{
required: true,
message:
"Please insert the phone number of the company!",
whitespace: true,
},
]}
>
<CustomInput
backgroundcolor={theme.white}
onChange={(event) => handleChange(event, "phone")}
value={state.phone}
/>
</Form.Item>
<Form.Item
name="Fax:"
label="Fax:"
rules={[
{
required: true,
message: "Please insert the fax of the company!",
whitespace: true,
},
]}
>
<CustomInput
backgroundcolor={theme.white}
onChange={(event) => handleChange(event, "fax")}
value={state.fax}
/>
</Form.Item>
<Form.Item
name="Manager name:"
label="Manager name:"
rules={[
{
required: true,
message:
"Please, insert the name of the company manager!",
whitespace: true,
},
]}
>
<CustomInput
backgroundcolor={theme.white}
onChange={(event) =>
handleChange(event, "managerName")
}
/>
</Form.Item>
<Form.Item
name="Manager e-mail:"
label="Manager e-mail:"
rules={[
{
type: "email",
message: "Invalid e-mail!",
},
{
required: true,
message:
"Please insert the e-mail of the company manager!",
},
]}
>
<CustomInput
backgroundcolor={theme.white}
onChange={(event) => handleChange(event, "email")}
/>
</Form.Item>
<Form.Item
name="Password:"
label="Password:"
rules={[
{
required: true,
message: "Please, insert a password!",
},
{
min: 4,
message: "Password needs to be at least 4 characters long",
},
]}
>
<CustomInput
backgroundcolor={theme.white}
onChange={(event) => handleChange(event, "password")}
type={"password"}
onKeyPress={verifyCredentials}
/>
</Form.Item>
<CustomButton
backgroundcolor={theme.primaryColor}
textcolor={theme.white}
onClick={() => {
signup();
}}
margintop={"13%"}
marginbottom={"13%"}
>
Register
</CustomButton>
</CustomForm>
)}
The major problem is that when companyDataFetched becomes true, the state containing the field values is not updated and the values of the fields don't update with the data fetched from the backend. How can I handle this issue?
[UPDATE AFTER PAIMAN'S SUGGESTION]
I tried using useEffect and another state for the API response, but the result is the same.
Here is my updated code:
useEffect(() => {
if (companyDataFetched) {
console.log(response);
setState((prevState) => {
return {
...prevState,
nume: response.name,
adresa: response.address,
telefon: response.phone,
fax: response.fax
}
});
}
}, [companyDataFetched, response]);
axios.get(`${process.env.REACT_APP_INTERNSHIP_API_URL}/Company/GetByCUI/${state.cif}`)
.then((res) => {
setResponse(res.data);
setCompanyDataFetched(true);
})
.catch((error) => {
console.log(error);
});
[UPDATE AFTER SANGEET'S SUGGESTION]
I tried using what you suggested. It did not work straight-forward, so I made some changes, but it doesn't work yet.
Here is my updated code:
useEffect(() => {
const fetchData = () => {
return axios
.get(`${process.env.REACT_APP_INTERNSHIP_API_URL}/Company/GetByCUI/${state.cif}`)
.then((res) => {
return res;
})
.catch((error) => {
console.log(error);
});
};
let isMounted = true;
if (companyDataFetched) {
(async () => {
const response = await fetchData();
if (isMounted) {
setState((prevState) => {
return {
...prevState,
name: response.data.name,
address: response.data.address,
phone: response.data.phone,
fax: response.data.fax,
};
});
} else {
setCompanyDataFetched(false);
}
})();
}
return function () {
isMounted = false;
};
}, [companyDataFetched, state.cif]);
CodePudding user response:
You should set your state in useEffect when companyDataFetched changed to true and also one state to save response data from api! so in onClick Function, you should save response to another state and setCompanyDataFetched to true to useEffect run then in useEffect change the value of state.
const [response , setResponse] = useState(null);
useEffect(() => {
if(companyDataFetched){
setState({
fax : response.fax,
phone : response.phone,
.........
})
}
},[companyDataFetched ])
CodePudding user response:
Don't have your API call in the onClick handler. Instead trigger the fetch in the button by saying setCompanyDataFetch(true).
Then have a useEffect with companyDataFetch as a dependency.
function fetchData() {
return axios.get(`${process.env.REACT_APP_INTERNSHIP_API_URL}/Company/GetByCUI/${state.cif}`)
.then((res) => {
// setResponse(res.data);
// setCompanyDataFetched(true);
return res;
})
.catch((error) => {
console.log(error);
});
}
Your useEffect looks like so
useEffect(() => {
let isMounted = true;
if (companyDataFetched) {
(async () => {
const response = await fetchData()
if (isMounted) setState((prevState) => {
return {
...prevState,
nume: response.name,
adresa: response.address,
telefon: response.phone,
fax: response.fax
}
});
if (isMounted) setCompanyDataFetched(false);
})();
}
return function () {
isMounted = false;
};
}, [companyDataFetched]);
