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]);
}
Related
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 have two API's
First API returns list of items which I am iterating to get each item's detailed data.
Here's the code
const [loader, setLoader] = useState(false);
React.useEffect(() => {
const fetchUsers = async() => {
setLoader(true);
const users = await getUsers();
const promises = users.map(async (user) => {
let userData = await getUsersDetailedData(user.userId);
return userData
});
let finalUsers = await Promise.all(promises);
setLoader(false);
}
fetchUsers();
}, [])
I am updating loader state before the api call and after call but it is not working.
Loader state is updating these many times and loader is not displaying
logs
Try it in this way,
React.useEffect(() => {
const fetchUsers = async() => {
const users = await getUsers();
const promises = users.map(async (user) => {
let userData = await getUsersDetailedData(user.userId);
return userData
});
let finalUsers = Promise.all(promises);
return finalUsers;
}
setLoader(true);
fetchUsers().then(res=>{
setLoader(false);
});
}, [])
function UserAccounts() {
const [accounts, setAccounts] = useState();
useEffect(() => {
async function fetchAccounts() {
const res = await fetch(
'https://proton.api.atomicassets.io/atomicassets/v1/accounts'
);
const { accounts } = await res.json();
setAccounts(accounts);
console.log(accounts);
}
fetchAccounts();
}, []);
}
I'm trying to understand why console.log shows nothing in this example and what is the correct way to console.log the data that is being fetched from the api.
Well, you need to get the structure of the returned payload from the API correct. It does not have an accounts property.
The payload looks like this:
{
"success":true,
"data":[{"account":"joejerde","assets":"11933"},{"account":"protonpunks","assets":"9072"}],
"queryTime": 1646267075822
}
So you can rename the data property while destructuring. const { data: accountList } = await res.json();
function UserAccounts() {
const [accounts, setAccounts] = useState();
useEffect(() => {
async function fetchAccounts() {
const res = await fetch(
'https://proton.api.atomicassets.io/atomicassets/v1/accounts'
);
const { data: accountList } = await res.json();
setAccounts(accountList);
// logging both the state and the fetched value
console.log(accounts, accountList);
// accounts (state) will be undefined
// if the fetch was successful, accountList will be an array of accounts (as per the API payload)
}
fetchAccounts()
}, [])
return <div>
{JSON.stringify(accounts)}
</div>
}
Edit: using some other variable name while destructuring, confusing to use the same variable name as the state (accounts).
Working codesandbox
One thing I would change is working with try/catch surrounding async/await statements.
If your await statement fails it will never reach the console.log statement.
Unless you have another component handling those errors, I would use it in that way.
That is my suggestion:
function UserAccounts() {
const [accounts, setAccounts] = useState();
useEffect(() => {
try {
async function fetchAccounts() {
const res = await fetch(
'https://proton.api.atomicassets.io/atomicassets/v1/accounts'
);
const { accounts } = await res.json();
setAccounts(accounts);
console.log(accounts);
}
} catch (err) {
console.log(err)
// do something like throw your error
}
fetchAccounts();
}, []);
}
since state function runs asyncronousely . therefore when you use setAccounts it sets accounts variable in async way , so there is a preferred way of doing this thing is as below
problems i seen
1.fetch result should destructured with data instead of accounts variable
2.setAccounts function is running async way so it will not print result immedietly in next line
import { useEffect, useState } from "react";
export default function App() {
const [accounts, setAccounts] = useState();
async function fetchAccounts() {
const res = await fetch(
"https://proton.api.atomicassets.io/atomicassets/v1/accounts"
);
const { data } = await res.json();
setAccounts(data);
}
// on component mount / onload
useState(() => {
fetchAccounts();
}, []);
// on accounts state change
useEffect(() => {
console.log(accounts);
}, [accounts]);
return <div className="blankElement">hello world</div>;
}
check here sample
I need to run this test case but currently it gets stuck in an infinite loop. This is the minimalistic code, Any suggestion is appreciated.
Test.tsx file:
it('verify useDeleteExclusions', async () => {
deleteExclusionDevices.mockResolvedValue([])
const {result} = renderHook(() => useDeleteExclusions(['1234']), {
wrapper: AllTheProviders,
})
act(() => {})
await waitFor(() => {
expect(result.current).toEqual({"errorExclusionIds": [], "loading": false, "successExclusionIds": []})
})
})
})
Hook that needs to be tested:
export function useDeleteExclusions(exclusionIds) {
const [response, setResponse] = useState<any>([])
useEffect(() => {
async function deleteExclusionDevicesAsync(exclusionIds) {
const res = await deleteExclusionDevices(exclusionIds)
}
deleteExclusionDevicesAsync(exclusionIds)
}, [exclusionIds])
return { response }
}
Api call function (used by hook):
export async function deleteExclusionDevices(exclusionIds: any): Promise<any> {
const token = await readToken()
const response = []
return response
}
Test gets stuck like this:
I have a component which displays products for a category. CategoryId is taken from subscribe method which is formed by pubsub pattern so I am waiting sub function to finish and passing to my API but it is not working on intial load of the page?
import { subscribe } from "./pubsub";
const Test = () => {
const [productId, setProductId] = useState({});
const [response, setResponse] = useState([]);
React.useEffect(() => {
function sub() {
return new Promise((resolve, reject) => {
subscribe("product-message", (data) => {
// console.log("Got some message", data);
// setProductId(data.productId);
resolve(data.productId);
});
});
}
async function fetchData() {
let message = await sub();
let response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${message.productId}` // Here I couldn't get the async data from above useEffect
);
console.log(response);
setResponse(response);
}
fetchData();
}, []);
return <div>{response.title}</div>; //It is not printing in intial load
};
export default Test;
So here is my sandbox link: https://codesandbox.io/s/happy-forest-to9pz?file=/src/test.jsx
If you only need the response, you do not need to store productId in state and then use it in another useEffeect to fetch data. You can simply implement the logic in one useEffec. Also note that you need to use the json response from fetch call so you need to use it like
let response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${productId}`
).then(res => res.json());
or
let res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${productId}`
)
let response = await res.json();
Complete function will look like
const Test = () => {
const [response, setResponse] = useState([]);
React.useEffect(() => {
async function fetchData(productId) {
let response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${productId}`
).then(res => res.json());
console.log(response);
setResponse(response);
}
console.log("Api calls");
subscribe("product-message", (data) => {
// console.log("Got some message", data);
fetchData(data.productId);
});
}, []);
return <div>{response.title}</div>;
};
export default Test;
However if you need productId in your application, you can go via a multiple useEffect approach like you have tried in your sandbox. Also make sure that you are using thee fetch call correctly and also make sure to not make the API call wheen productId is not available
const Test = () => {
const [productId, setProductId] = useState({});
const [response, setResponse] = useState([]);
React.useEffect(() => {
console.log("Api calls");
subscribe("product-message", (data) => {
// console.log("Got some message", data);
setProductId(data.productId);
});
}, []);
React.useEffect(() => {
async function fetchData() {
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${productId}` // Here I couldn't get the async data from above useEffect
);
const response = await res.json();
console.log(response);
setResponse(response);
}
if(productId) {
fetchData();
}
}, [productId]);
return <div>{response.title}</div>;
};
export default Test;
Working Sandbox