I'm trying to build a react-native weather app, fetching data from the openweather api using hooks like this:
useEffect(() => {
async function fetchWeather() {
try {
let data = await fetch(URL);
const json = await data.json();
dispatch({type: 'success', payload: json});
} catch (error) {
dispatch({type: 'failure'});
}
}
fetchWeather();
}, []);
This only loads the data once. I want to make sure the weather info stays up to date. What is the best way to refresh the data? Do I poll every X minutes (if so how)?
Are you looking for a period of time, you will call api?
try it:
const [isStatus, setStatus] = useState(true)
setInterval(()=> {
setStatus(!isStatus)
}, 3000)
useEffect(() => {
fetchWeather();
}, [isStatus])
or you only can use this function:
useEffect(() => {
let loop = setInterval(()=> {
fetchWeather();
}, 3000);
return () => clearInterval(loop);
}, [])
My example applies when the application is opening
Related
const [dataSortArray, setDataSortArray] = useState([]);
// seeAllFeeds
const { data, loading, refetch, fetchMore } = useQuery(SEE_ALL_FEEDS_QUERY, {
variables: {
offset: 0,
},
});
useEffect(() => {
if (!loading) {
refetch();
console.log(data);
setDataSortArray(data);
console.log("✅", dataSortArray);
}
}, []);
as you can see I use useQuery.
if loading is done, i refetch the query and console.log(data) then data contains some array.
and I put this array into dataSortArray using setDataSortArray.
but when I console.log dataSortArray, it's empty.
✅ Array []
do you know why?
put loading into dependency array it will invoke every time value of loading is change passing Empty array [] as dependency array means it will render only once
useEffect(() => {
if (!loading) {
refetch();
console.log(data);
setDataSortArray(data);
console.log("✅", dataSortArray);
}
}, [loading]);
As #Dominic have pointed out in the question's comments (with a relevant link to another SO thread), useState is not effective immediately. So if you call setState on varA and console.log immediately afterwards, the varA won't be in the latest state you would expect.
I would suggest handling the state of dataSortArray in a separate useEffect.
useEffect(() => {
if (!loading) {
refetch();
console.log(data);
setDataSortArray(data);
}
}, []);
// Log dataSortArray when processing for setDataSortArray is completed by React.
useEffect(() => {
console.log(dataSortArray)
}, [dataSortArray]}
Im working with my friends on a app project and we find an issue many times when we tring to set a use state and the console log the variable, i've looking for a solution in the website and saw that the reason is that the usestate is an async awiat which means the variable that i set in the use state isn't immidatly set in it, so i tried many solution that i found in the websites but none of them work for me.
in the screenShot you can see that the json variable is in console log before and the set varaible doesn't show after the setActiveUser , any help?
Thanks!
If you want to do something upon setting state then your best bet is to use the useEffect hook from React and adding your state in the dependency array. This will then call the function in your useEffect every time the state changes. See below a rough example:
import { Text } from 'react-native';
const MyComponent = () => {
const [activeUser, setActiveUser] = useState(null);
useEffect(() => {
// This should log every time activeUser changes
console.log({ activeUser });
}, [activeUser]);
const fetchAuthentication = async user => {
var flag = false;
await fetch('/api/authUser/', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user),
})
.then(res => {
res.ok && (flag = true);
return res.json();
})
.then(json => {
if (flag) {
setActiveUser(json);
}
})
.catch(error => console.error(error));
return flag;
};
return <Text>Hi</Text>;
};
Full documentation: https://reactjs.org/docs/hooks-effect.html
I'm currently learning React Native (Expo).
I want to use redux and react-native-firebase.
When I subscribe to firebase (onSnapshot) at startup of my app, it returns the data from firebase. But since onSnapchot doesn't return a promise, I can't use it for my app-loading component.
Therefore, I also need to fetch the data from firebase to prevent the app from flicker.
The result is that at startup of my app I fetch the data twice.
So my question is:
How can I wait for onSnapshot loading my data from firebase?
Thanks
const Manager = (props) => {
//STATE
const [init, setInit] = useState(false);
//HOOKS
const fetchData = useFetchData();
useInitFirebaseSubscriptions();
//FUNCTIONS
async function onInit() {
console.log('[MANAGER]: loading app...');
await Promise.all([fetchData()]);
}
function onFinishedInit() {
console.log('[MANAGER]: ...app loading successfull!');
setInit(true);
}
//RETURN
if (!init) {
return <AppLoading startAsync={onInit} onFinish={onFinishedInit} onError={console.warn} />;
} else {
return props.children;
}
};
export default Manager;
//INITIAL FETCH BEFORE RENDERING
export function useFetchData() {
const dispatch = useDispatch();
return async function () {
try {
await firestore()
.collection('users')
.get()
.then((querySnapshot) => dispatch(actions.fetch(querySnapshot)));
} catch (err) {
console.log(err.message);
}
};
}
//INIT SUBSCRIPTIONS TO FIREBASE
export function useInitFirebaseSubscriptions() {
const dispatch = useDispatch();
useEffect(() => {
console.log('[CONTROLLER]: subscribed to Firebase');
const unsubscribe = firestore()
.collection('users')
.onSnapshot(
(querySnapshot) => dispatch(action.fetch(querySnapshot)),
(error) => console.log(error)
);
return () => {
unsubscribe();
console.log('[CONTROLLER]: unsubscribed from Firebase');
};
}, []);
}
[MANAGER]: loading app...
[MANAGER]: subscribed to Firebase
[USER_REDUCER]: fetched data
[USER_REDUCER]: fetched data
[MANAGER]: ...app loading successfull!
I think you can accomplish your goal by adding some "loading" state in redux for when you are actively fetching data from firebase. Add the state and reducer cases specific to this data fetching/loading.
Example code:
export function useInitFirebaseSubscriptions() {
const dispatch = useDispatch();
useEffect(() => {
console.log('[CONTROLLER]: subscribed to Firebase');
dispatch(action.startFetch()); // <-- dispatch starting data fetch
const unsubscribe = firestore()
.collection('users')
.onSnapshot(
(querySnapshot) => {
dispatch(action.fetch(querySnapshot));
dispatch(action.completedFetch()); // <-- done fetching
},
(error) => {
console.log(error);
dispatch(action.completedFetch()); // <-- done fetching
},
);
return () => {
unsubscribe();
console.log('[CONTROLLER]: unsubscribed from Firebase');
};
}, []);
};
Select the loading state from the redux store and conditionally render the loading UI, otherwise render the passed children.
const Manager = (props) => {
const isFetchingData = useSelector(state => state.isFetchingData);
if (isFetchingData) {
return <AppLoadingIndicator />;
}
return props.children; // *
};
* Generally you may use some additional conditional rendering here depending on if data was actually fetched/returned and is just empty, or if there was an error, etc... basically provide a bit of a result status.
I'm experiencing a strange behaviour with created() and mounted() in Vue.js. I need to set 2 lists in created() - so it means those 2 lists will help me to create a third list which is a merge.
Here is the code :
// return data
created () {
this.retrieveSellOffers();
this.getAllProducts();
},
mounted () {
this.mergeSellOffersProducts();
},
methods: {
retrieveSellOffers() {
this.sellerId = localStorage.sellerId;
SellOfferServices.getAllBySellerId(this.sellerId)
.then((response) => {
this.sellOffers = response.data;
console.log("this.sellOffers");
console.log(this.sellOffers);
})
.catch((e) => {
console.log(e);
});
},
getAllProducts() {
ProductServices.getAll()
.then((response) => {
this.products = response.data;
console.log("this.products");
console.log(this.products);
})
.catch((e) => {
console.log(e);
});
},
mergeSellOffersProducts () {
console.log(this.products) // print empty array
console.log(this.sellOffers) // print empty array
for (var i = 0; i < this.sellOffers.length; i++) {
if (this.sellOffers[i].productId === this.products[i]._id) {
this.arr3.push({id: this.sellOffers[i]._id, price: this.sellOffers[i].price, description: this.products[i].description});
}
}
this.arr3 = this.sellOffers;
},
}
//end of code
So my problem is when I enter in mergeSellOffersProducts(), my 2 lists are empty arrays :/
EDIT :
This way worked for me :
async mounted() {
await this.retrieveSellOffers();
await this.getAllProducts();
this.mergeSellOffersProducts();
},
methods: {
async retrieveSellOffers() {
this.sellerId = localStorage.sellerId;
this.sellOffers = (await axios.get('link/api/selloffer/seller/', { params: { sellerId: this.sellerId } })).data;
},
async getAllProducts() {
this.products = (await axios.get('link/api/product')).data;
},
}
I think the reason is: Vue does not wait for the promises to resolve before continuing with the component lifecycle.
Your functions retrieveSellOffers() and getAllProducts() contain Promise so maybe you have to await them in the created() hook:
async created: {
await this.retrieveSellOffers();
await this.getAllProducts();
}
So I tried to async my 2 methods :
async retrieveSellOffers() {
this.sellerId = localStorage.sellerId;
this.sellOffers = (await axios.get('linkhidden/api/selloffer/', { params: { sellerId: '615b1575fde0190ad80c3410' } })).data;
console.log("this.sellOffers")
console.log(this.sellOffers)
},
async getAllProducts() {
this.products = (await axios.get('linkhidden/api/product')).data;
console.log("this.products")
console.log(this.products)
},
mergeSellOffersProducts () {
console.log("here")
console.log(this.sellOffers)
console.log(this.products)
this.arr3 = this.sellOffers;
},
My data are well retrieved, but yet when I enter in created, the two lists are empty...
You are calling a bunch of asynchronous methods and don't properly wait for them to finish, that's why your data is not set in mounted. Since Vue does not await its lifecycle hooks, you have to deal with the synchronization yourself.
One Vue-ish way to fix it be to replace your method mergeSellOffersProducts with a computed prop (eg mergedSellOffersProducts). Instead of generating arr3 it would simply return the merged array. It will be automatically updated when products or sellOffers is changed. You would simply use mergedSellOffersProducts in your template, instead of your current arr3.
If you only want to update the merged list when both API calls have completed, you can either manually sync them with Promise.all, or you could handle this case in the computed prop and return [] if either of the arrays is not set yet.
When you're trying to merge the 2 lists, they aren't filled up yet. You need to await the calls.
async created () {
await this.retrieveSellOffers();
await this.getAllProducts();
},
async mounted () {
await this.mergeSellOffersProducts();
},
I am using an api call to get information for my app which I display to the user. The problem is that when I open the screen for the first time the app displays the information but when I go to a different screen and then comeback I dont see the information unless I restart the app.
This function makes the apiCall for me:
async function getOrders() {
var retrieveData = async () => {
try {
var value = await AsyncStorage.getItem("user");
var data = JSON.parse(value);
return data.user.email;
} catch (error) {
alert(error);
}
};
retrieveData().then((usr) => {
setUser(usr)
fetch(URL + "/api/order/quoted", {
method: "POST",
body: "user=" + usr,
headers: { "Content-type": "application/x-www-form-urlencoded" },
})
.then((response) => response.json())
.then((responseJson) => {
if (responseJson.error === null) {
setOrders(responseJson.orders);
}
});
});
}
First I use the retriveData function to get the used id, based on that information is server to the user.
You are using react-navigation version 5, so you need to wrap your logic fetch data in useFocusEffect hook react navigation docs
import { useFocusEffect } from '#react-navigation/native';
useFocusEffect(
React.useCallback(() => {
getOrders()
}, [getOrders])
);
The problem can be solved in the following steps:
If you want the data fetched from your endpoint to be used even if you move to other screen use Redux.
If you use redux or not and want to fetch the api every time you open a specific screen then you need to add an onfocus listener. An example is here https://reactnavigation.org/docs/navigation-events/
class Profile extends React.Component {
componentDidMount() {
this._unsubscribe = navigation.addListener('focus', () => {
// do something
});
}