how I can use setTimeout here in asyncStorage call - javascript

I am trying to get data from local memory using asyncStorage but there is one issue
useEffect( async () => {
try {
if(activemanagegroup !== null) {
var groupValue = JSON.stringify(activemanagegroup)
await AsyncStorage.setItem('managementGroup', groupValue)
}
var listValue = JSON.stringify(list)
await AsyncStorage.setItem('selectedList', listValue)
} catch (e) {
console.log('Failed to save data')
}
},[activemanagegroup, list])
useEffect(() => {
async function getData() {
try {
const managementGroupValue = await AsyncStorage.getItem('managementGroup')
const managedUsersList = await AsyncStorage.getItem('selectedList')
const activeManagementGroupSelected = managementGroupValue != null ? JSON.parse(managementGroupValue) : null
const activeList = managedUsersList != null ? JSON.parse(managedUsersList) : null
setActiveManagementGroup(activeManagementGroupSelected)
setNewList(activeList)
} catch (error) {
console.log('error getting data', error)
}
}
getData()
},[activemanagegroup])
the problem is selectedList updates a second later after managementGroup and due to that I end up getting old selectedList. How I can delay the call and make sure I get updated selectedList ?
Note: I am storing both these values once user presses a button.

I wouldn't recommend using AsyncStorage to retrieve the data more than once; once you have the data loaded initially, you should use React's built-in state management solutions to store the data instead of re-reading it from AsyncStorage.
Thus, I'd move your getItem calls to a separate useEffect that only runs once and updates the local React state:
const [managementGroupValue, setManagementGroupValue] = useState(null)
const [managedUsersList, setManagedUsersList] = useState(null)
useEffect(() => {
async function getData() {
try {
setManagementGroupValue(await AsyncStorage.getItem('managementGroup'))
setManagedUsersList(await AsyncStorage.getItem('selectedList'))
} catch (error) {
console.log('error getting data', error)
}
}
getData()
}, [])
and then use the managementGroupValue and managedUsersList variables to refer to that data instead of retrieving it from AsyncStorage each time.

Related

Function not getting called in useEffect()

I want these two functions to be called every time the component renders, but they are not being executed. And when I put the functions in the dependency array it results in an infinite loop. Any idea why they are not being called?
function PortfolioComponent() {
const [requestedAssets, setRequestedAssets] = useState([]);
const [assets, setAssets] = useState([]);
useEffect(() => {
async function calcValue() {
Promise.all(
requestedAssets.map(async function (asset) {
try {
const response = await axios.get(assetData(asset.AssetId));
let cp = response.data.market_data.current_price.eur;
let value = Number(cp) * Number(asset.Amount);
return { ...asset, value: value, price: cp };
} catch (error) {
console.log(error.response.data.error);
throw error;
}
})
)
.then((newAssetArray) => {
setAssets(newAssetArray);
console.log(newAssetArray);
console.log(assets);
})
.catch((error) => {
console.log(error);
});
}
async function getAssets() {
try {
const response = await axios.get("http://localhost:4200/assets");
// Do as you wish with response here
const assetResponse = response.data.rows;
setRequestedAssets(assetResponse);
console.log(requestedAssets);
} catch (error) {
console.log(error.response.data.error);
}
}
getAssets();
calcValue();
}, []);
Also some weird behaviour I just discovered...
For example, this line of code:
let cp = await response.data.market_data.current_price.eur;
When I remove the await keyword and save it in VS code, the data is retrieved as expected. However, when I refresh the browser the arrays are empty again. The same goes for when I add the await keyword again and save. The same thing happens.
This is what worked for me. So, instead of having a useState variable for requestedAssets, I created a variable inside the getAssets method instead. I'm not exactly sure why this works and not the other way. But, if anybody could explain, that would be great.
function PortfolioComponent() {
//const [requestedAssets, setRequestedAssets] = useState([]);
const [assets, setAssets] = useState([]);
useEffect(() => {
async function getAssets() {
const response = await axios.get("http://localhost:4200/assets");
const requestedAssets = response.data.rows;
console.log(requestedAssets);
Promise.all(
requestedAssets.map(async function (asset) {
try {
const response = await axios.get(assetData(asset.AssetId));
let cp = response.data.market_data.current_price.eur;
let value = Number(cp) * Number(asset.Amount);
return { ...asset, value: value, price: cp };
} catch (error) {
console.log(error.response.data.error);
throw error;
}
})
)
.then((newAssetArray) => {
setAssets(newAssetArray);
console.log(newAssetArray);
console.log(assets);
})
.catch((error) => {
console.log(error);
});
}
getAssets();
}, []);
The recommendation is to declare your functions inside the useEffect, see the official documentation. If you keep scrolling in the docs, they even have an example similar to yours, with an async function.
If, for some reason, you do need to have your function declared outside the useEffect, you can use a useCallback, which allows you to declare them in the dependency array. Something like this:
const getAssets = useCallback(async() => {
try {
const response = await axios.get("http://localhost:4200/assets");
// Do as you wish with response here
const assetResponse = response.data.rows;
setRequestedAssets(assetResponse);
console.log(requestedAssets);
} catch (error) {
console.log(error.response.data.error);
}
}, [requestedAssets])
useEffect(() => {
getAssets()
}, [getAssets])
You can also see the section Do I need to specify functions as effect dependencies or not? in this blog here for more information.
PS: This blog is from Dan Abramov, one of the creators of React, so reliable source ;)

Passing external data into components

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();
}, [])

Is there a way to load state using AsyncStorage through Context?

I am trying to load an array I saved using AsyncStorage back into state but I cant seem to get it working. I am passing the array from AsyncStorage back into context and calling the load_state case.
function loadList() {
try {
const data = AsyncStorage.getItem('data')
console.log(data)
loadState(JSON.parse(data))
}
catch (error) {
console.log(error)
}
}
const loadState = dispatch => {
return (value) => {
dispatch({ type: 'load_state', payload: value})
}}
case 'load_state':
console.log(action.payload.value)
return [...state, ...action.payload.value]

Perform two functions simultaneously

I have a function that refreshes the data of my component when the function is called. At this moment it only works for one component at a time. But I want to refresh two components at once. This is my refresh function:
fetchDataByName = name => {
const { retrievedData } = this.state;
const { fetcher } = this.props;
const fetch = _.find(fetcher, { name });
if (typeof fetch === "undefined") {
throw new Error(`Fetch with ${name} cannot be found in fetcher`);
}
this.fetchData(fetch, (error, data) => {
retrievedData[name] = data;
this._isMounted && this.setState({ retrievedData });
});
};
My function is called like this:
refresh("meetingTypes");
As it it passed as props to my component:
return (
<Component
{...retrievedData}
{...componentProps}
refresh={this.fetchDataByName}
/>
);
I tried passing multiple component names as an array like this:
const args = ['meetingTypes', 'exampleMeetingTypes'];
refresh(args);
And then check in my fetchDataByName function if name is an array and loop through the array to fetch the data. But then the function is still executed after each other instead of at the same time. So my question is:
What would be the best way to implement this that it seems like the
function is executed at once instead of first refreshing meetingTypes
and then exampleMeetingTypes?
Should I use async/await or are there better options?
The fetchData function:
fetchData = (fetch, callback) => {
const { componentProps } = this.props;
let { route, params = [] } = fetch;
let fetchData = true;
// if fetcher url contains params and the param can be found
// in the component props, they should be replaced.
_.each(params, param => {
if (componentProps[param]) {
route = route.replace(`:${param}`, componentProps[param]);
} else {
fetchData = false; // don't fetch data for this entry as the params are not given
}
});
if (fetchData) {
axios
.get(route)
.then(({ data }) => {
if (this.isMounted) {
callback(null, data);
}
})
.catch(error => {
if (error.response.status == 403) {
this._isMounted && this.setState({ errorCode: 403 });
setMessage({
text: "Unauthorized",
type: "error"
});
}
if (error.response.status == 401) {
this._isMounted && this.setState({ errorCode: 401 });
window.location.href = "/login";
}
if (error.response.status != 403) {
console.error("Your backend is failing.", error);
}
callback(error, null);
});
} else {
callback(null, null);
}
};
I assume fetchData works asynchronously (ajax or similar). To refresh two aspects of the data in parallel, simply make two calls instead of one:
refresh("meetingTypes");
refresh("exampleMeetingTypes");
The two ajax calls or whatever will run in parallel, each updating the component when it finishes. But: See the "Side Note" below, there's a problem with fetchDataByName.
If you want to avoid updating the component twice, you'll have to update fetchDataByName to either accept multiple names or to return a promise of the result (or similar) rather than updating the component directly, so the caller can do multiple calls and wait for both results before doing the update.
Side note: This aspect of fetchDataByName looks suspect:
fetchDataByName = name => {
const { retrievedData } = this.state; // <=============================
const { fetcher } = this.props;
const fetch = _.find(fetcher, { name });
if (typeof fetch === "undefined") {
throw new Error(`Fetch with ${name} cannot be found in fetcher`);
}
this.fetchData(fetch, (error, data) => {
retrievedData[name] = data; // <=============================
this._isMounted && this.setState({ retrievedData });
});
};
Two problems with that:
It updates an object stored in your state directly, which is something you must never do with React.
It replaces the entire retrievedData object with one that may well be stale.
Instead:
fetchDataByName = name => {
// *** No `retrievedData` here
const { fetcher } = this.props;
const fetch = _.find(fetcher, { name });
if (typeof fetch === "undefined") {
throw new Error(`Fetch with ${name} cannot be found in fetcher`);
}
this.fetchData(fetch, (error, data) => {
if (this._isMounted) { // ***
this.setState(({retrievedData}) => ( // ***
{ retrievedData: {...retrievedData, [name]: data} } // ***
); // ***
} // ***
});
};
That removes the in-place mutation of the object with spread, and uses an up-to-date version of retrievedData by using the callback version of setState.

firebase return onSnapshot promise

I'm using firebase/firestore and I'm looking a way to return promise of snapshot.
onlineUsers(){
// i want to return onSnapshot
return this.status_database_ref.where('state','==','online').onSnapshot();
}
in other file I did
componentDidMount(){
// this.unsubscribe = this.ref.where('state','==','online').onSnapshot(this.onCollectionUpdate)
firebaseService.onlineUsers().then(e=>{
console.log(e)
})
}
I get the errors
Error: Query.onSnapshot failed: Called with invalid arguments.
TypeError: _firebaseService2.default.unsubscribe is not a function
if i do this way
onlineUsers(){
return this.status_database_ref.where('state','==','online').onSnapshot((querySnapshot)=>{
return querySnapshot
})
}
I get
TypeError: _firebaseService2.default.onlineUsers(...).then is not a function
in addition,
when I do this way
this.unsubscribe = firebaseService.onlineUsers().then((querySnapshot)=>{
console.log(querySnapshot.size)
this.setState({count:querySnapshot.size})
})
// other file
onlineUsers(callback) {
return this.status_database_ref.where('state', '==', 'online').get()
}
it not listen to change into firebase, means if I change in firebase it's not update or change the size..
---- firestore function ---
I tried to make firestore function that trigger each time the UserStatus node updated but this take some seconds and it slow for me.
module.exports.onUserStatusChanged = functions.database
.ref('/UserStatus/{uid}').onUpdate((change, context) => {
// Get the data written to Realtime Database
const eventStatus = change.after.val();
// Then use other event data to create a reference to the
// corresponding Firestore document.
const userStatusFirestoreRef = firestore.doc(`UserStatus/${context.params.uid}`);
// It is likely that the Realtime Database change that triggered
// this event has already been overwritten by a fast change in
// online / offline status, so we'll re-read the current data
// and compare the timestamps.
return change.after.ref.once("value").then((statusSnapshot) => {
return statusSnapshot.val();
}).then((status) => {
console.log(status, eventStatus);
// If the current timestamp for this data is newer than
// the data that triggered this event, we exit this function.
if (status.last_changed > eventStatus.last_changed) return status;
// Otherwise, we convert the last_changed field to a Date
eventStatus.last_changed = new Date(eventStatus.last_changed);
// ... and write it to Firestore.
//return userStatusFirestoreRef.set(eventStatus);
return userStatusFirestoreRef.update(eventStatus);
});
});
function to calculate and update count of online users
module.exports.countOnlineUsers = functions.firestore.document('/UserStatus/{uid}').onWrite((change, context) => {
const userOnlineCounterRef = firestore.doc('Counters/onlineUsersCounter');
const docRef = firestore.collection('UserStatus').where('state', '==', 'online').get().then(e => {
let count = e.size;
return userOnlineCounterRef.update({ count })
})
})
A Promise in JavaScript can resolve (or reject) exactly once. A onSnapshot on the other hand can give results multiple times. That's why onSnapshot doesn't return a promise.
In your current code, you're left with a dangling listener to status_database_ref. Since you don't do anything with the data, it is wasteful to keep listening for it.
Instead of using onSnapshot, use get:
onlineUsers(callback){
this.status_database_ref.where('state','==','online').get((querySnapshot)=>{
callback(querySnapshot.size)
})
}
Or in your original approach:
onlineUsers(){
return this.status_database_ref.where('state','==','online').get();
}
I know it's too late but here is my solution using TypeScript & Javascript.
TYPESCRIPT
const _db=firebase.firestore;
const _collectionName="users";
onDocumentChange = (
document: string,
callbackSuccess: (currentData: firebase.firestore.DocumentData, source?: string | 'Local' | 'Server') => void,
callbackError?: (e: Error) => void,
callbackCompletion?: () => void
) => {
this._db.collection(this._collectionName).doc(document).onSnapshot(
{
// Listen for document metadata changes
includeMetadataChanges: true
},
(doc) => {
const source = doc.metadata.hasPendingWrites ? 'Local' : 'Server';
callbackSuccess(doc.data(), source);
},
(error) => callbackError(error),
() => callbackCompletion()
);
};
JAVASCRIPT (ES5)
var _this = this;
onDocumentChange = function (document, callbackSuccess, callbackError, callbackCompletion) {
_this._db.collection(_this._collectionName).doc(document).onSnapshot({
// Listen for document metadata changes
includeMetadataChanges: true
}, function (doc) {
var source = doc.metadata.hasPendingWrites ? 'Local' : 'Server';
callbackSuccess(doc.data(), source);
}, function (error) { return callbackError(error); }, function () { return callbackCompletion(); });
};
I found a way to do that
onlineUsers(callback){
return this.status_database_ref.where('state','==','online').onSnapshot((querySnapshot)=>{
callback(querySnapshot.size)
})
}
componentDidMount(){
this.unsubscribe = firebaseService.onlineUsers(this.onUpdateOnlineUsers);
console.log(this.unsubscribe)
}
onUpdateOnlineUsers(count){
this.setState({count})
}
componentWillUnmount(){
this.unsubscribe();
}

Categories