How to execute code after another part is completed EXPO REACT NATIVE - react-native

I'm trying to save record into database, if this record is not in user's profile (he did not discover this place) but also this record exists in collection of all places.
I'm using Expo react native and I think my problem is, that if condition will execute before functions recordInUsersAccount and recordInGlobalDatabase . Is there any way how to ensure execution after these two functions are copleted? In functions I'm rewriting variables in state={}, so I can check them below. (I tried .then() and await, async but I was not succesful).
Thank you very much.
saveScannedQrCode(idOfPlace) {
this.recordInUsersAccount(idOfPlace);
this.recordInGlobalDatabase(idOfPlace);
if (!this.state.placeAlreadyScanned && this.state.placeExistInDatabase) {
// we will add this record into database
} else {
// we will NOT add this record into database
}
}
This is the code of functions:
recordInUsersAccount(idOfPlace) {
const userId = auth.currentUser.uid;
const usersRef = db
.collection("placesExploredByUsers") // default
.doc("mUJYkbcbK6OPrlNuEPzK") // default
.collection("s53sKFeF5FS0DjuI2cdO1Rp9sCS2") // uid
.doc(idOfPlace); // id of place
usersRef.get().then((docSnapshot) => {
if (docSnapshot.exists) {
this.setState({
placeAlreadyScanned: true, // place is in user's database
});
} else {
this.setState({
placeAlreadyScanned: false, // place is NOT in user's database
});
}
});
}
recordInGlobalDatabase(idOfPlace) {
const usersRef = db
.collection("databaseOfPlaces") // default
.doc(idOfPlace); // id of place
usersRef.get().then((docSnapshot) => {
if (docSnapshot.exists) {
this.setState({
placeExistInDatabase: true, // place is in global database of places
});
} else {
this.setState({
placeExistInDatabase: false, // place is NOT in global database of places
});
}
});
}

The problem with the code is that setState in React is async, and you're trying to check the values straight after executing the functions which modify the state.
Assuming that your methods work fine and do what they're supposed to do, you could do something like:
Leave your methods as they are right now, modifying the state.
Call saveScannedQRCode as your are doing now, triggering both of the helper methods.
Instead of checking the state right after calling them, you could do that in the componentDidUpdate lifecycle hook.
componentDidUpdate(prevProps, prevState) {
if (prevState.placeExistInDatabase !== this.state.placeExistInDatabase && prevState.placeAlreadyScanned !== this.state.placeAlreadyScanned) {
// do something here - at this point state is already updated and ready to use.
// you could check for the values you're waiting for to update the DB, else not do anything.
}
}
saveScannedQrCode(idOfPlace) {
this.recordInUsersAccount(idOfPlace);
this.recordInGlobalDatabase(idOfPlace);
}
One thing - be sure to reset the state (e.g. set it to null) once you've processed the update, this way your componentDidUpdate hook won't have any problems and your strict equality will be fine.

Related

Issue rendering data from firestore document in react native

I created a map for the array of exercises in my database, and then for each exercise, which is a document reference, I'm getting the data from that document reference and setting it to a state. This is resulting in an infinite loop right now.
If I remove the setExerciseData line, the console logs the exercise object's data that I'm expecting to see. I'm really not sure what the correct way to render the name field from this data is.
{workout.exercises.map((exercise) => {
async function getData(exercise) {
getDoc(exercise).then((doc) => {
console.log(doc.data());
setExerciseData(doc.data());
});
}
getData(exercise);
return (
<Text>{exerciseData.name}</Text>
)
})}
You need to use useEffect() and setState() to be able to render your data. Also, Firebase Firestore is Asynchronous in nature, as a general note, no one should be trying to convert an Async operation into a sync operation as this will cause problems. You need to use an Asynchronous function to fetch data from Firestore. See sample code below:
const getExerciseData = async () => {
const docRef = doc(db, "<collection-name>", '<document-id>')
const docSnap = await getDoc(docRef)
if (docSnap.exists()) {
// console.log("Document data:", docSnap.data())
setExerciseData(docSnap.data())
} else {
// doc.data() will be undefined in this case
console.log("No such document!")
}
}
useEffect(() => {
getExerciseData()
}, [])
return (
<Text>{exerciseData.name}</Text>
)
You could also check my answer on this thread for more use-cases.

How to execute vue-router beforeEach ONE time?

I have an annoying situation where I need to hook into every route change to check something. If condition X is true, I need to redirect to the homepage by showing a notice only on that single pageview. So I have the following:
router.beforeEach(() => {
if (...) {
store.showIneligibleBanner();
return { path: '/' };
}
// in all other cases hide the banner
store.hideIneligibleBanner();
return true;
});
The problem is when I return { path: '/' } this triggers the beforeEach a second time and the conditional no longer applies. I know I could create more variables to keep track but I wanted a cleaner approach.
I am really just trying to show a banner a single time on that return redirect.
In store's state, store two vars: hasBannerBeenShown and isBannerVisible
router:
// ...
router.beforeEach((to, from, next) => {
if (!store.hasBannerBeenShown && anyOtherFancyCondition) {
store.hasBannerBeenShown = true;
store.isBannerVisible = true;
return { path: '/' };
}
store.isBannerVisible && (store.isBannerVisible = false);
next()
});
Note: The syntax above is based on the assumption you're using pinia. If you're using vuex, the syntax changes a bit (you'll need to commit mutations rather than assign to state), but the logic stays the same.
That method takes a to (and from) parameters, which are the . So you can check if to.path === '/'.

Which Lifecycle hook after axios get but before DOM render

I'm trying to render my DOM, dependent on some data I'm returning from an axios get. I can't seem to get the timing right. The get is in the created hook, but there is a delay between the get and actually receiving the data. Basically if there is info in seller_id then I need to show the cancel button, otherwise don't. Here is my code:
this is in my created hook
axios.get('https://bc-ship.c9users.io/return_credentials').then(response => {
this.seller_id = response.data.seller_id;
this.selected_marketplace = response.data.marketplace;
this.token = response.data.auth_token;
});
and then this is the logic to show or hide the button. I've tried created, mounted, beforeUpdate, and updated all with no luck. I've also tried $nextTick but I can't get the timing correct. This is what I have currently:
beforeUpdate: function () {
// this.$nextTick(function () {
function sellerIdNotBlank() {
var valid = this.seller_id == '';
return !valid;
}
if(sellerIdNotBlank()){
this.show_cancel_button = true;
}
// })
},
First, it is pointless to get your data from backend and try to sync with Vue.js lifecycle methods. It never works.
Also, you should avoid beforeUpdate lifecycle event. It is often a code smell. beforeUpdate is to be used only when you have some DOM manipulations done manually and you need to adjust them again before Vue.js attempt to re-render.
Further, show_cancel_button is a very good candidate for a computed property. Here is how component will look:
const componentOpts = {
data() {
return {
seller_id: '',
// ... some more fields
};
},
created() {
axios.get('https://bc-ship.c9users.io/return_credentials').then(response => {
this.seller_id = response.data.seller_id;
this.selected_marketplace = response.data.marketplace;
this.token = response.data.auth_token;
});
},
computed: {
show_cancel_button() {
return this.seller_id !== '';
}
}
}

Handle errors in react native and redux

I have a react native application using redux for state management.
On every API request, I have actions dispatched which is handled by reducer. This is then forwarded to saga and back to reducer.
I am using snackbar to show any errors which might have happened.
e.g. In the login flow, one of the operations is to fetch the OTP for username.
The action types are:
GET_OTP_FOR_USER_REQUEST
GET_OTP_FOR_USER_SUCCESS
GET_OTP_FOR_USER_FAILURE
The initial state for the reducer (LoginReducer) is
{
hasError: false,
error : {
display_message : "",
details : null
}
otherData : []
}
Now in case of GET_OTP_FOR_USER_FAILURE, the reducer will be updated to
{
hasError: true,
error : {
display_message : "Invalid Username",
details : null
}
otherData : []
}
In my view component, I conditionally render the snackbar based on the hasError flag
{this.props.hasError ? Snackbar.show({
title: this.props.error.display_message,
duration: Snackbar.LENGTH_LONG
}) : null}
If I don't reset the hasError, then the snackbar keeps coming for every setState call (which happens on textinputchange).
The current approach I am using is, I have an action to RESET_ERROR.
I call this action once the setState is called on the textbox on the component (in this case the username)
onUserNameTextChanged = (text) => {
this.props.resetError(); //Reset the error
this.setState({ //something });
}
The issue with this is that this.props.resetError(); will get called on every character update.
Just to ensure that I don't call render multiple time, I am using shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState){
if(this.props.hasError && (this.props.hasError === nextProps.hasError))
return false;
else
return true;
}
I am not sure if there is simpler or cleaner approach to handle these scenarios. Any directions will be helpful.
You can just conditionally call it if there's an error, that way you're not always invoking it:
onUserNameTextChanged = (text) => {
if (this.props.hasError) {
this.props.resetError(); //Reset the error
}
this.setState({ //something });
}
You can also tie it into the success action if you want that error to show until the API returns successfully.

Realm & React Native - Best practice to implement auto-updates?

What are the best practices/patterns make realm a reactive datasource in a react native app? Especially for presentational and container components pattern?
Here is an example which I'd like to make reactive: Realm with React Native
The docs on auto-updates/change-events are a bit thin and the official example does not make use of this feature (to my knowledge).
You can make your example reactive by subscribing to events and updating the ui when you receive a change event. Right now events are only sent when write transactions are committed, but finer grained change events will be added in the future. For now you could add the following constructor to update the ui on changes:
constructor(props) {
super(props);
this.realm = new Realm({schema:[dogSchema]})
this.realm.addListener('change', () => {
this.forceUpdate()
});
}
You need to hold onto a Realm instance to keep the notifications alive, and you can use this Realm instance throughout the rest of the component.
Instead of calling forceUpdate, you could instead set the component's state or props within the event listener to trigger the refresh, like so:
constructor(props) {
super(props);
this.realm = new Realm({schema:[dogSchema]})
this.state = {...}; // Initial state of component.
this.realm.addListener('change', () => {
this.setState({...}); // Update state instead of using this.forceUpdate()
});
}
I think #Ari gave me a good answer for redux folks as i was also struggling. I'm not sure if it's immutable enough but it works!
I'm simpliy dispatching getVehicles action inside addListener and it just works!
Below is UI component whose constructor function makes the magic!
//- importing my realm schema
import realm from '../../db/models';
//- Importing my action
import { getVehicles } from './../../actions/vehicle';
#connect((store) => {
return {
vehicle: store.vehicle.vehicles
}
})
export default class Devices extends Component {
constructor(props) {
super(props);
realm.addListener('change', () => {
props.dispatch(getVehicles());
});
}
}
Below is db/models file used up there in the constructor.
import Realm from 'realm';
class VehicleSchema {};
VehicleSchema = {
name: 'vehicleInfo',
properties: {
vehicleName: 'string',
vehicleNumber: 'string',
vehiclePassword: 'string',
vehiclePasswordTrigger: 'bool',
vehicleType: 'string',
vehiclePicture: { type: 'data', optional: true }
}
};
export default new Realm({schema: [VehicleSchema]});
Below is the actions/vehicle file, which gets dispatched in the constructor above.
import { queryVehicle } from './../db/queryVehicle';
export function getVehicles() {
const vehicles = queryVehicle();
return function(dispatch) {
dispatch({type: "GOT_VEHICLES", payload: vehicles});
}
}
Below is my queryVehicle function that does the querying called in action file above.
import vehicleModel from './models';
const queryVehicle = (queryInfo="vehicleInfo", filter='') => {
const objects = vehicleModel.objects(queryInfo);
if(filter.length === 0) return objects;
let results = objects.filtered(filter);
return results;
};
export { queryVehicle };
disclaimer I don't know if this code looks immutable enough, or following good redux practice cause i'm just starting out with redux so give me some comments advising if i'm doing something wrong.
I'll also guess reducer implementation wouldn't matter much in this here.
Recently ran into an issue with Realm ListView auto-updating. When the ListView rows have varied heights, you can get overlaps on rows in the UI. The below was the only way I could get the ListView to re-render without causing UI overlaps. It seems a bit "dirty" to me, so if there is a better way, I welcome the input. But this is working perfectly so far; incase anyone else runs into this issue.
Basically it just wipes the dataSource, then inserts it again using the setState callback when there are insertions or deletions, but modifications simply roll through and auto-update.
let feed = this.props.store.feed;
feed.addListener((name, changes) => {
if (changes.insertions.length || changes.deletions.length) {
this.setState({dataSource: this.ds.cloneWithRows([])},
() => this.setState({dataSource: this.ds.cloneWithRows(feed)})
);
} else {
this.setState({dataSource: this.ds.cloneWithRows(feed)});
}
});