I have problem with async await approach in react. I want to run async function after click ing button and it works but looks like sometimes promise won't be resolved properly. It seems like typical await/async/then problem. I've never really worked with it in react so my knowledge here is pretty non-existent.
Here is my main code. It sets state to url and execute api call after clicking button. It works 70% of time.
import { useApi } from "../../services/UrlShortener/useApi";
import { shortenUrl } from "../../services/UrlShortener/UrlShortenerService";
export const Index = () => {
const [url, setUrl] = useState("");
const [shortenedUrl, setShortenedUrl] = useState("");
const getShortUrl = useApi(shortenUrl);
const getShortenedUrl = async () => {
if (url) {
await getShortUrl
.request(url) //request is promise.
.then(() => setShortenedUrl(getShortUrl.data?.value));
// console.log(shortenedUrl);
//setShortenedUrl(getShortUrl.data.value);
} else console.log("error");
console.log(getShortUrl);
};
return (
<>
<button
className="index__shortener__button"
onClick={() => getShortenedUrl()}
>
<BiCut size="2em" />{" "}
<span className="index__shortener__text"> Shorten URL</span>
</button>
<input
className="index__shortener__input"
type="text"
placeholder="Enter your original URL eg.http://interia.pl/news/nie_uwierzysz"
required
onChange={(e) => setUrl(e.target.value)}
/>
<p className="index__shortener__shortener_link">
<a aria-label={shortenedUrl} href={shortenedUrl}>
{shortenedUrl}
</a>
</p>
</>
);
};
Where useApi is reusable hook and shortenUrl is call to api:
export const shortenUrl = async (url) => await axios.post(`${SERVICE_API_URL}`, { value: url});
Typical reusable hook used to fetch data.
export const useApi = (apiFunc) => {
const request = async (...args) => {
const [data, setData] = useState(null);
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const request = async (...args) => {
setLoading(true);
try {
const result = await apiFunc(...args);
setData(result.data);
} catch (err) {
setError(err.message || "Unexpected Error!");
} finally {
setLoading(false);
}
};
return {
data, //data from api
error,
loading,
request //axios promise function
};
I see that data is sometimes null/undefined and trying to figure out how it works.
//@Konrad Linkowski approach, sadly arg will be undefined here always because i'm using internal state for useApi
await getShortUrl.request(url)
.then((a)=>{
console.log('getShortUrl', getShortUrl);
console.log("arg",a);
setShortenedUrl(a.data?.value);
})
//console logs
getShortUrl {data: {…}, error: '', loading: false, request: ƒ}data: {value: 'http://localhost:5000/api/urlShortener/oovybnzibofi'}error: ""loading: falserequest: async ƒ ()[[Prototype]]: Object
Index.js:19 arg undefined
CodePudding user response:
Correct way
Seems like your useApi
returns a promise, but doesn't do anything with it.
The correct way to do this in react is just to wait for state to update:
import { useApi } from "../../services/UrlShortener/useApi";
import { shortenUrl } from "../../services/UrlShortener/UrlShortenerService";
export const Index = () => {
const [url, setUrl] = useState("");
// get request and data from useApi, rename data to shortenedUrl for convenience
// shortenedUrl will update automatically after request is called
const { request, data: shortenedUrl } = useApi(shortenUrl);
const getShortenedUrl = () => {
if (url) {
// this request function returns a promise, but this promise doesn't do anything, so no need to await
request()
} else {
console.log("error");
}
};
// ...
};