How to wait for a useState update within onSubmit? - reactjs

I want to fetch metadata from a website, then upload it to the database.
The website link comes from a form input field.
Since useState update is async, the data is not yet present in the formData object on submit.
What options do I have?
npm package in use: https://www.npmjs.com/package/suq
const [title, setTitle] = useState("");
const [url, setUrl] = useState("");
const [imgUrl, setImgUrl] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
suq(
`${urlToPost}`,
function (err, json, body) {
if (!err) {
setTitle(json.opengraph["og:title"]);
setUrl(json.opengraph["og:url"])
setImgUrl(json.opengraph["og:image"]);
}
}
);
const postData = {
title,
url,
imgUrl
};
db.collection("posts")
.doc()
.set(postData)
.then(() => {
console.log("Document successfully written!");
})
.catch((error) => {
console.error("Error writing document: ", error);
});
}

what about this:
const [title, setTitle] = useState("");
const [url, setUrl] = useState("");
const [imgUrl, setImgUrl] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
let submitTitle = '';
let submitUrl = '';
let submitImgUrl = '';
suq(
"https://www.space.com/first-structures-in-universe-revealed",
function (err, json, body) {
if (!err) {
submitTitle = json.opengraph["og:title"];
submitUrl = json.opengraph["og:url"];
submitImgUrl = json.opengraph["og:image"];
setTitle(submitTitle);
setUrl(submitUrl)
setImgUrl(submitImgUrl);
}
}
);
const formData = {
title: submitTitle,
url: submitUrl,
imgUrl: submitImgUrl
};
console.log(formData);
// title, url and imgUrl are still at initial value
}
the local state updates will be available in the next render but the values you need will be available to you in your handle submit function

We can't handle the wait for the state update directly in the same call.
As the state will update its value after the re-render cycle.
So it's upon you what would you like to do with the updated state so I can help you.

Related

Map data on runtime after post request

I have three apis in all. GetAssets is the first, followed by assetsOptionsList and getAssetsLibrary. The issue I'm having is that when I post the data on getAssetsLibrary, I want to be able to present it on get Assets at runtime.Everything is working fine but i want to show assets on runtime.
I'm setting the runTime state true on get request but the problem is it works only for one time.Second time, it does not map on runtime. Actually, i want to know is there any alternative so that i can achieve the goal.
In the below code the one function is getting the assets. And i want to run the one function when the post request successfully sent.
const [images, setImages] = useState([]);
const [assetOptions, setAssetOptions] = useState([]);
const [faqOpened, setToggleFaq] = useState(false);
const [runTime, setRunTime] = useState(false)
const [assetID, setAssetID] = useState()
const [isLoading, setIsLoading] = useState(false);
const handleForm = (e) => {
const index = e.target.selectedIndex;
const el = e.target.childNodes[index]
const option = el.getAttribute('id');
setAssetID(option)
}
const formHandler = (e) => {
e.preventDefault()
let formData = new FormData();
formData.append('media', e.target.media.files[0]);
formData.append('assetListId', assetID)
formData.append('name', e.target.name.value);
console.log(Object.fromEntries(formData))
const res = axios.post('api/asset-library',
formData
).then((response) => {
showSuccessToaster(response?.data?.message)
setRunTime(true)
setToggleFaq(false)
})
.catch((error) => {
showErrorToaster(error?.response?.data?.message)
})
}
const showSuccessToaster = (response) => {
return uploadToasterSuccess.show({ message: response });
}
const showErrorToaster = (error) => {
return uploadToasterError.show({ message: error });
}
const one = async () => {
setIsLoading(true)
const data = await axios.get('api/assets').then((res) => {
return res?.data?.data
})
setImages(data)
setIsLoading(false)
}
const two = async () => {
const data = await axios.get('/api/asset-list').then((res) => {
return res?.data?.data
})
setAssetOptions(data)
}
useEffect(() => {
one()
two()
}, [runTime]);

Axios not showing informations on react page

I would like to use axios to fetch the values from API in react.js, and set it as a form, but it doesn't display any fetched data at all.
export default function Review() {
const [fetchedData, setFetchedData] = useState([]);
const [fetchedlanguage, setlanguage] = useState([]);
useEffect(() => {
const getStudent = async () => {
const stu = await axios.get('http://localhost:8000/students/');
setFetchedData(stu.data.students[0]);
setlanguage(stu.data.students[0].languages)
};
getStudent()
},[]);
console.log("student: ", fetchedData);
const [formdata, setformdata] = useState({
availability: 6,
preference:'201, 301',
experience:'201',
language:fetchedlanguage[0],
background:fetchedData.background,
});
Even though the console.log shows the data correctly, when I set the form here, how come there is no updates on data?
Control it all in one place. You will want to spread the original values over the setformdata because it's immutable. I'm not sure what all the API returns so continue to override each formdata property that you get back from the API.
export default function Review() {
const [formdata, setformdata] = useState({
availability: 6,
preference:'201, 301',
experience:'201',
language: 'english',
background: 'initial-background',
});
useEffect(() => {
const getStudent = async () => {
const stu = await axios.get('http://localhost:8000/students/');
const student = stu.data.students.length > 0 ? stu.data.students[0] : {};
setFormData({
...formdata,
langauge: student.languages,
// TODO: continue to override the formData from student returned from API
});
};
getStudent()
}, []);
// TODO: use formdata to feed into form
return null;
}

useState initial value don't use directly the value

I have an initial state that I never use directly in the code, only inside another set value state
Only a scratch example:
interface PersonProps {}
const Person: React.FC<PersonProps> = () => {
const [name, setName] = useState<string>("")
const [todayYear, setTodayYear] = useState<string>("")
const [birthYear, setBirthYear] = useState<string>("")
const [age, setAge] = useState<string>("")
const getPerson = async () => {
try {
const response = await getPersonRequest()
const data = await response.data
setName(data.name)
setTodayYear(data.today_year)
setBirthYear(data.future_year)
setAge(data.todayYear - data.birthYear)
} catch (error) {
console.log(error)
}
}
useEffect(() => {
getPerson()
})
return (
<h1>{name}</h1>
<h2>{age}</h2>
)
}
export default Person
In this case as you can see I will never use "todayYear" and "birthYear" on UI, so code give a warning
todayYear is assigned a value but never used
What can I do to fix this and/or ignore this warning?
If you don't use them for rendering, there's no reason to have them in your state:
const Person: React.FC<PersonProps> = () => {
const [name, setName] = useState<string>("")
const [age, setAge] = useState<string>("")
const getPerson = async () => {
try {
const response = await getPersonRequest()
const data = await response.data
setName(data.name)
setAge(data.todayYear - data.birthYear)
} catch (error) {
console.log(error)
}
}
useEffect(() => {
getPerson()
})
return (
<h1>{name}</h1>
<h2>{age}</h2>
)
}
Side note: In most cases, you can leave off the type argument to useState wen you're providing an intial value. There's no difference between:
const [name, setName] = useState<string>("")
and
const [name, setName] = useState("")
TypeScript will infer the type from the argument. You only need to be explicit when inference can't work, such as if you have useState<Thingy | null>(null).
As this other answer points out, unless you want your code to run every time your component re-renders (which would cause an infinite render loop), you need to specify a dependency array. In this case, probably an empty one if you only want to get the person information once.
Also, since it's possible for your component to be unmounted before the async action occurs, you should cancel your person request if it unmounts (or at least disregard the result if unmounted):
const Person: React.FC<PersonProps> = () => {
const [name, setName] = useState<string>("");
const [age, setAge] = useState<string>("");
const getPerson = async () => {
const response = await getPersonRequest();
const data = await response.data;
return data;
};
useEffect(() => {
getPerson()
.then(data => {
setName(data.name)
setAge(data.todayYear - data.birthYear)
})
.catch(error => {
if (/*error is not a cancellation*/) {
// (Probably better to show this to the user in some way)
console.log(error);
}
});
return () => {
// Cancel the request here if you can
};
}, []);
return (
<h1>{name}</h1>
<h2>{age}</h2>
);
};
If it's not possible to cancel the getPersonRequest, the fallback is a flag:
const Person: React.FC<PersonProps> = () => {
const [name, setName] = useState<string>("");
const [age, setAge] = useState<string>("");
const getPerson = async () => {
const response = await getPersonRequest();
const data = await response.data;
return data;
};
useEffect(() => {
let mounted = true;
getPerson()
.then(data => {
if (mounted) {
setName(data.name)
setAge(data.todayYear - data.birthYear)
}
})
.catch(error => {
// (Probably better to show this to the user in some way)
console.log(error);
});
return () => {
mounted = false;
};
}, []);
return (
<h1>{name}</h1>
<h2>{age}</h2>
);
};
I also would like to mention one more thing. It's not related to your question but I think it's important enough to talk about it.
you need to explicitly state your dependencies for useEffect
In your case, you have the following code
useEffect(() => {
getPerson()
})
it should be written as follow if you want to trigger this only one time when a component is rendered
useEffect(() => {
getPerson()
}, [])
or if you want to trigger your side effect as a result of something that has changed
useEffect(() => {
getPerson()
}, [name])
If this is not clear for I suggest read the following article using the effect hook

how do I handle parameters from OnChange - ReactJS?

I am trying to handle the data sent via onChange in order to make api calls based on the dropdown options , but I want to handle a default value if there is nothing sent to the api call
async function fetchFeed(domain) {
return api.get(`http://localhost:8002/api/v1/xxx/list/?domain=${domain}`);
}
export default function Board() {
const [isModalOpen, setModalIsOpen] = useState(false);
const [users, setUsers] = useState([]);
const [responseData, setResponseData] = useState([]);
// fetches data
const fetchData = (domain) => {
fetchFeed(domain)
.then((response) => {
setResponseData(response.data.results);
})
.catch((error) => {
console.log(error);
});
};
const handleChange = (e) =>{
fetchData(e.target.value);
}
useEffect(() => {
const domain = ??
fetchData(domain);
}, []);

Array and Objects of states in React

So I have an array of objects and each property in the object comes from the user. User chooses an object that he wants to edit and I am passing it using context to component that takes data from user. Now after getting data I am inserting it back to array but it is giving me wrong data.
Here is component that takes data from user.
const SelectedSymptom = () => {
const [
selected,
setSelected,
selectedSymptom,
setSelectedSymptom,
] = useContext(symptomContext);
const [note, setNote] = useState("");
const [since, setSince] = useState("");
const [severity, setSeverity] = useState("");
const [feature, setFeature] = useState("");
const [place, setPlace] = useState("");
const [colour, setColour] = useState("");
useEffect(() => {
return async () => {
await setSelectedSymptom((prev) => {
return {
...prev,
feature,
since,
severity,
place,
colour,
note,
};
});
await setSelected((prev) => {
const cur = prev.filter((item) => item.name !== selectedSymptom.name);
if (selectedSymptom !== "") cur.push(selectedSymptom);
return cur;
});
console.log(selectedSymptom, selected);
};
}, [since, feature, severity, place, colour]);
}
Data from form is coming correctly but I guess due to async nature of setState call, I am getting error.
First remove the return from useEffect. Return is a cleanup method for useEffect, example to terminate intervals, listeners etc.
I changed to a constant instead update() which I run in the end of the useEffect.
Also the parameters in the code below, you only had set the key, not the value to the keys
await setSelectedSymptom((prev) =>
return {
...prev,
feature: feature,
since: since,
severity: severity,
place: place,
colour: colour,
note: note,
};
});
I hope this brings some clarification
const SelectedSymptom = () => {
const [
selected,
setSelected,
selectedSymptom,
setSelectedSymptom,
] = useContext(symptomContext);
const [note, setNote] = useState("");
const [since, setSince] = useState("");
const [severity, setSeverity] = useState("");
const [feature, setFeature] = useState("");
const [place, setPlace] = useState("");
const [colour, setColour] = useState("");
useEffect(() => {
const update = async () => {
await setSelectedSymptom((prev) => {
return {
...prev,
feature: feature,
since: since,
severity: severity,
place: place,
colour: colour,
note: note,
};
});
await setSelected((prev) => {
const cur = prev.filter((item) => item.name !== selectedSymptom.name);
if (selectedSymptom !== "") cur.push(selectedSymptom);
return cur;
});
update();
};
}, [since, feature, severity, place, colour]);
}

Resources