I'm trying to call an endpoint in an api that returns a value, I have a dream to call this fetch function inside another function, then save it to a variable.
But this returns a promise or if I get it working the function calls other things before the returned value.
Is the only way to do a timeout?
Here is a code example
async function someFetch() {
const res = await fetch("someurl");
const data = res.json();
return data;
}
function useFetched(someInput1, someInput2) {
const fetchedData = someFetch(); // need this input before anything else is called
const some_var = fetchedData + someInput1 + someInput2;
return some_var;
}
I've also tried to make the second function async and called await in front of someFetch(), but this returns a promise.
You need need to await the result of someFetch
async function useFetched(someInput1, someInput2){
const fetchedData = await someFetch() // need this input before anything else is called
const some_var = fetchedData + someInput1 + someInput2
return some_var
}
You cant make async useFetch[hook]. You need to use a callback here. The rough idea, you can pass setter function to useFetch to set value. And then can use that value.
Sample:
async function fetchTodo() {
const res = await fetch("someurl");
const data = res.json();
return data;
}
function useFetched(id, callback) {
useEffect(() => {
fetchTodo(id).then(data => {
console.log(data);
callback(data);
});
}, [id, callback]);
}
Working sample:
import React, { useEffect, useState } from "react";
async function fetchTodo(id) {
const res = await fetch("https://jsonplaceholder.typicode.com/todos/" + id);
const data = res.json();
return data;
}
function useFetched(id, callback) {
useEffect(() => {
fetchTodo(id).then(data => {
console.log(data);
callback(data);
});
}, [id, callback]);
}
export default function App() {
const [data, setData] = useState({});
useFetched(1, setData);
if (!data) return <h1>Loading...</h1>;
return (
<div className="App">
<h1>{JSON.stringify(data)}</h1>
</div>
);
}
Sandbox: https://codesandbox.io/s/misty-smoke-ls2y4?file=/src/App.js:0-607
Related
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
In my react app, I am currently passing a list of stores by calling the API directly from the URL.
const getStore = async () => {
try {
const response = axios.get(
'http://localhost:3001/appointment-setup/storeList'
);
return response;
} catch (err) {
console.error(err);
return false;
}
};
I pass this function into my useEffect hook where I would set my get a list of stores using resp.data.stores:
const [storeLocations, setStoreLocations] = useState([]);
useEffect(() => {
async function getData(data) {
await service.stepLocation.init();
const resp = await getStore();
setStoreLocations(resp.data.stores);
}
setFlagRender(true);
return getData();
}, []);
This works, however, I noted in useEffect there is a call await service.stepLocation.init(). There is a file that already takes care of all the backend/data for the component.
const stepLocation = {
// removed code
// method to retrieve store list
retrieveStoreList: async function ()
let response = await axios.get(
constants.baseUrl + '/appointment-setup/storeList'
);
return response.data.stores;
,
// removed code
Since this data is available, I don't need the getStore function. However when I try to replace response.data.stores in useEffect with service.stepLocation.retrieveStoreList no data is returned. How do I correctly pass the data from this file in my useEffect hook?
I think your useEffect should be like follows as you want to save the stores in your state.
useEffect(() => {
const updateStoreLocations = async () => {
const storeLocations = await service.stepLocation.retrieveStoreList();
setStoreLocations(storeLocations);
}
updateStoreLocations();
}, [])
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
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;
}
I'm attempting to store data in AsyncStorage and load them back(obviously). The .setItem function works, and the notification pops up at the bottom of the iOS simulator when I call it. However, the .getItem function doesn't work, and when I console.log it, returns undefined. I have two functions to store and fetch the data:
setData = (rawDataToStore, keyToStore) => {
data_store = JSON.stringify(rawDataToStore);
AsyncStorage.setItem(keyToStore, data_store, () => {
console.warn('Stored data!')
} )
}
getData = (keyToSearch) => {
AsyncStorage.getItem(keyToSearch).then(storage => {
parsed_data = JSON.parse(storage);
return parsed_data
}).catch(e => console.warn(e))
}
I just tested the functions in my render():
to save the data:
this.setData({value: 1}, "test_data");
to load the data:
console.log(this.getData("test_data"));
The console.log just returns undefined.
I'm completely new to Asyncstorage, but what am I doing wrong?
Your console.log returns undefined ... cause, your setData function hasn't finished its job yet ... you have to await for it first, cause it's an async operation.
class YourComponent extends React.Component {
async componentDidMount() {
await this.setData();
const data = await this.getData();
console.log('data returned', data);
}
setData = async (rawDataToStore, keyToStore) => {
data_store = JSON.stringify(rawDataToStore);
await AsyncStorage.setItem(keyToStore, data_store);
};
getData = async keyToSearch => {
let parsed_data = null;
const storage = await AsyncStorage.getItem(keyToSearch);
if (storage) {
parsed_data = JSON.parse(storage);
console.log('Data in AsyncStorage: ', parsed_data);
}
return parsed_data;
};
}