I am trying to call Async function and use its response inside of useRef in react like the below:
const myComponent = () => {
const getTitle = async () => {
const res = await fetch('title');
return res;
}
const myref = useRef(
Employee().retire({
id: 1,
title: await getTitle(),
}),
);
}
However, I cannot use await inside of useRef. What other way to accomplish this?
Set the ref
const myRef = useRef(null);
And then use useEffect to update it once when the component renders:
// `getTitle` doesn't need to be async because
// you're apparently already returning a promise
function getTitle() {
return fetch('title');
}
useEffect(() => {
// Call an async function in your `useEffect`
// to wait for the promise to resolve and use the data
// to update the ref
async function getData() {
const title = await getTitle();
myRef.current = Employee().retire({ id: 1, title });
}
getData();
}, []);
The only way I know is set myref after async function is solved. Something like:
const myComponent = () => {
const getTitle = async () => {
const res = await fetch('title');
return res;
}
const myref = useRef(null);
useEffect(() => {
(async () => {
let title = await getTitle();
myref.current = Employee().retire({ id: 1, title });
console.log(myref.current);
})();
}, []);
}
Here a working example.
await keyword using only inside of async function
Related
I have a custom hook that saves/loads from cacheStorage.
const useCache = (storageKey, dir) => {
const [cache, setCache] = useState()
useEffect(() => {
const openCache = async () => {
const c = await caches.open(storageKey)
setCache(c)
}
openCache()
},[])
const save = async (res) => {
console.log(cache)
//prints undefined
cache.put(dir, new Response(JSON.stringify(res)))
const load = async () => {
console.log('load', cache)
//prints undefined
const res = await cache.match(dir)
return await res.json()
}
return { save, load }
}
and this useCache hook is used inside of another custom hook useConfigs
const useConfigs = (key, defaultValue = false) => {
const { save, load } = useCache('configs', '/configs')
(...)
const getConfigs = async () => {
const res = await fetchFromNetwork()
save(res)
}
const getLocalConfigs = async () => {
const res = await load()
(...)
return res
}
The issue is that useState variable cache returned null when both save() and load() are called inside getConfigs() and getLocalConfigs(). Seems like the value is undefined because of closure. If that is the reason, what would be the solution to update the cache variable by the time save() and load() are called?
I have problem with async function. I need track.user in another function but my func getTracks() async. I don't have clue how can i get this.
const Player = ({trackUrl, index, cover, id}) => {
const [track, setTrack] = useState({})
const [user, setUser] = useState({})
useEffect(() => {
const getTracks = async () => {
await httpClient.get(`/track/${id}`)
.then((response) => {
setTrack(response.data);
})
}
getTracks();
getUser() // track.user undefined
}, [])
const getUser = async() => {
await httpClient.get(`/profile/${track.user}/`)
.then((response) => {
setUser(response.data);
})
}
}
I would declare both functions at the beginning of the component (you can later optimise them with useCallback but it's not that important in this phase).
const getTracks = async () => {
await httpClient.get(`/track/${id}`)
.then((response) => {
setTrack(response.data);
})
}
const getUser = async() => {
await httpClient.get(`/profile/${track.user}/`)
.then((response) => {
setUser(response.data);
})
}
I would then call an async function inside the useEffect hook. There are a couple of ways of doing it: you can either declare an async function in the useEffect hook and call it immediately, or you can call an anonymous async function. I prefer the latter for brevity, so here it is:
useEffect(() => {
(async () => {
await getTracks();
getUser();
})();
}, []);
Now when you call getUser you should be sure that getTracks has already set the track variable.
Here is the complete component:
const Player = ({trackUrl, index, cover, id}) => {
const [track, setTrack] = useState({})
const [user, setUser] = useState({})
const getTracks = async () => {
await httpClient.get(`/track/${id}`)
.then((response) => {
setTrack(response.data);
})
}
const getUser = async() => {
await httpClient.get(`/profile/${track.user}/`)
.then((response) => {
setUser(response.data);
})
}
useEffect(() => {
(async () => {
await getTracks();
getUser();
})();
}, []);
}
EDIT 07/18/22
Following Noel's comments and linked sandbox, I figured out that my answer wasn't working. The reason why it wasn't working is that the track variable was't available right after the getTrack() hook execution: it would have been available on the subsequent render.
My solution is to add a second useEffect hook that's executed every time the track variable changes. I have created two solutions with jsonplaceholder endpoints, one (see here) which preserves the most of the original solution but adds complexity, and another one (here) which simplifies a lot the code by decoupling the two methods from the setTrack and setUser hooks.
I'll paste here the simpler one, adapted to the OP requests.
export default function Player({ trackUrl, index, cover, id }) {
const [track, setTrack] = useState({});
const [user, setUser] = useState({});
const getTracks = async () => {
// only return the value of the call
return await httpClient.get(`/track/${id}`);
};
const getUser = async (track) => {
// take track as a parameter and call the endpoint
console.log(track, track.id, 'test');
return await httpClient.get(`profile/${track.user}`);
};
useEffect(() => {
(async () => {
const trackResult = await getTracks();
// we call setTrack outside of `getTracks`
setTrack(trackResult);
})();
}, []);
useEffect(() => {
(async () => {
if (track && Object.entries(track).length > 0) {
// we only call `getUser` if we are sure that track has at least one entry
const userResult = await getUser(track);
console.log(userResult);
setUser(userResult);
}
})();
}, [track]);
return (
<div className="App">{user && user.id ? user.id : "Not computed"}</div>
);
}
You can move the second request to the then block of the dependent first request,i.e., getTracks.
Also, you shouldn't mix then and await.
useEffect(() => {
const getTracks = () => {
httpClient.get(`/track/${id}`)
.then((response) => {
setTrack(response.data);
httpClient.get(`/profile/${response.data.user}/`)
.then((response) => {
setUser(response.data);
})
})
}
getTracks();
}, [])
You shouldn't be mixing thens with async/await. You should be using another useEffect that watches out for changes in the track state and then calls getUser with that new data.
function Player(props) {
const { trackUrl, index, cover, id } = props;
const [ track, setTrack ] = useState({});
const [ user, setUser ] = useState({});
async function getTracks(endpoint) {
const response = await httpClient.get(endpoint);
const data = await response.json();
setTrack(data);
}
async function getUser(endpoint) {
const response = await httpClient.get(endpoint);
const data = await response.json();
setUser(data);
}
useEffect(() => {
if (id) getTracks(`/track/${id}`);
}, []);
useEffect(() => {
if (track.user) getUser(`/profile/${track.user}`);
}, [track]);
}
I Dont know what is going on but even after using async and await keyword still the length is showing zero. Thanks in advance.
const commercial_shoots = [];
let test;
React.useEffect(() => {
async function fetchData() {
const app_ref = ref(storage, "Home/");
await listAll(app_ref)
.then((res) => {
res.items.forEach((itemRef) => {
getDownloadURL(itemRef).then((url) => {
commercial_shoots.push({ img: url });
});
});
})
.catch((error) => {
console.log(error);
});
}
fetchData();
}, []);
return <div>{commercial_shoots.length}</div>;
};
React only re-renders the component when state or props updates. Here, you are only updating a local variable. So, even when it updates, the UI does not reflect the change.
The solution would be to use commercialShoots as a state in the component.
const CommercialShoots = () => {
const [commercialShoots, setCommercialShoots] = useState([]);
useEffect(() => {
async function fetchData() {
try {
const app_ref = ref(storage, "Home/");
const res = await listAll(app_ref);
const downloadUrls = await Promise.all(res.items.map(itemRef) => getDownloadURL(itemRef));
const mappedUrlsToImg = downloadUrls.map((url) => ({ img: url });
setCommercialShoots(mappedUrlsToImg);
} catch (error) {
console.error(error);
}
}
fetchData();
}, []);
return <div>{commercial_shoots.length}</div>;
};
NOTE - Since we are using async / await extensively, I took the liberty of updating the .then() to async / await syntax.
I made this store:
export class CommentStore {
comments = []
constructor() {
makeAutoObservable(this, {}, { autoBind: true });
}
async loadPostComments(postId: number): Promise<void> {
const res = await API.loadPostComments(postId);
runInAction(() => {
this.comments = res;
});
}
async sendComment(postId: number, comment: Comment): Promise<void> {
try {
await API.sendComment(postId, comment);
await this.loadPostComments(postId);
return true;
} catch (err) {
console.log('oops');
}
}
}
Do i need use await in react components? For example:
useEffect(() => {
(async function () {
await loadPostComments(postId);
})();
}, [loadPostComments, postId]);
But this also works fine:
useEffect(() => {
loadPostComments(postId);
}, [loadPostComments, postId]);
Same for sendComment onClick:
onClick={()=>{sendComment(postId, comment)}}
onClick={async ()=>{await sendComment(postId, comment)}}
So, is it necessary to use await in this situations?
You want to await something only if it is necessary, e.g. when the next line of code uses the data from Promise.
In the useEffect case that you provided it is not necessary and on onClick handlers as well
Yes it is unnecessary to write async/await on them.
you just have to write the async call on the functions and that is enough.
for example:
const [posts, setPosts] = useState([]);
useEffect(() => {
const loadPost = async () => {
// Await make wait until that
// promise settles and return its result
const response = await axios.get(
"https://jsonplaceholder.typicode.com/posts/");
// After fetching data stored it in some state.
setPosts(response.data);
}
// Call the function
loadPost();
}, []);
`
there is no need to write promise and async / await on everything, remember ;P
I have a customHook with using useEffect and I would like it to return a result once useEffect is done, however, it always return before my async method is done...
// customHook
const useLoadData = (startLoading, userId, hasError) => {
const [loadDone, setLoadDone] = useState(false);
const loadWebsite = async(userId) => {
await apiService.call(...);
console.log('service call is completed');
dispatch(someAction);
}
useEffect(() => {
// define async function inside useEffect
const loadData = async () => {
if (!hasError) {
await loadWebsite();
}
}
// call the above function based on flag
if (startLoading) {
await loadData();
setLoadDone(true);
} else {
setLoadDone(false);
}
}, [startLoading]);
return loadDone;
}
// main component
const mainComp = () => {
const [startLoad, setStartLoad] = useState(true);
const loadDone = useLoadData(startLoad, 1, false);
useEffect(() => {
console.log('in useEffect loadDone is: ', loadDone);
if (loadDone) {
// do something
setStartLoad(false); //avoid load twice
} else {
// do something
}
}, [startLoad, loadDone]);
useAnotherHook(loadDone); // this hook will use the result of my `useLoadData` hook as an execution flag and do something else, however, the `loadDone` always false as returning from my `useLoadData` hook
}
It seems in my useDataLoad hook, it does not wait until my async function loadData to be finished but return loadDone as false always, even that I have put await keyword to my loadData function, and setLoadDone(true) after that, it still returns false always, what would be wrong with my implementation here and how could I return the value correct through async method inside customHook?
Well...it seems to be working after I put the setLoadDone(true); inside my async method, not inside useEffect, although I am not sure why...
updated code:
// customHook
const useLoadData = (startLoading, userId, hasError) => {
const [loadDone, setLoadDone] = useState(false);
const loadWebsite = async(userId) => {
await apiService.call(...);
console.log('service call is completed');
dispatch(someAction);
setLoadDone(true);
}
useEffect(() => {
// define async function inside useEffect
const loadData = async () => {
if (!hasError) {
await loadWebsite();
}
}
// call the above function based on flag
if (startLoading) {
await loadData();
// setLoadDone(true); doesn't work here
}
}, [startLoading]);
return loadDone;
}