React Native state console.log - firebase

I am using firebase for my app and the data i read i want to put that in state to use it in different places.
it kinda works but when i want to console.log the state it updates like 30 times a second, am i doing something wrong?
this is my code
const db = firebase.firestore();
const [PBS1Detail, setPBS1Detail] = useState();
db.collection('Track').get().then((snapshot) => {
snapshot.docs.forEach(doc => {
renderTracks(doc)
}
)
});
const renderTracks = (doc) => {
let data = doc.data().data[0].Module;
return setPBS1Detail(data);
}
console.log(PBS1Detail)
i already tried to store it in a variable instead of state but thats not working for me, i can't get the variable from the function global
i am a noob i get it xd

You don't need a return statement when setting state. Also, it looks like you're performing some async function which means that when your component renders for the first time, PBS1Detail will be undefined since the db is a pending Promise.
You could/should put your async functions in useEffect hook:
useEffect(()=> {
db.collection('Track').get().then((snapshot) => {
snapshot.docs.forEach(doc => {
renderTracks(doc)
}
)
});
}, [])
const renderTracks = (doc) => {
let data = doc.data().data[0].Module;
setPBS1Detail(data);
}
Finally, your renderTracks function doesn't seem correct as it appears you're looping over docs and resetting your state each time.
Instead, maybe consider having an array for PBS1Detail
const [PBS1Detail, setPBS1Detail] = useState([]);
Then modify your async call:
useEffect(()=> {
db.collection('Track').get().then((snapshot) => {
let results = []
snapshot.docs.forEach(doc => {
results.push(renderTracks(doc))
}
)
setPBS1Detail(results)
});
}, [])
const renderTracks = (doc) => {
return doc.data().data[0].Module;
}
This way you're only setting state once and thus avoiding unnecessary re-renders and you're saving all of your docs instead of overwriting them.

Related

RTK Query — correct approach for creating endpoint data selectors

I've been playing a bit with RTK Query and I was wondering how to correctly pick data from an endpoint and returning that data filtered (or somehow updated)
I ended up making this to work
export const selectGroupAvailableAssessments = (cacheKey) => {
return rtkQueryApi.endpoints.getGroupAvailableAssessments.select(cacheKey);
};
// returns object with status, endpointName, data, error, isLoading, etc
export const selectGroupAvailableAssessmentsByAssessmentId = (cacheKey, assessmentId) => createSelector(
selectGroupAvailableAssessments(cacheKey),
(availableAssessments) => {
if (!availableAssessments.data) return null;
const { data } = availableAssessments;
return data.find((item) => item.id === assessmentId);
},
);
// returns the selector "data" assessments filtered by id
The in the component
const assessmentById = useSelector(selectGroupAvailableAssessmentsByAssessmentId(cacheKey, assessmentId));
Is this the correct approach for creating selectors in RTK Query? I'm not sure if I'm correct.
Used these links as reference
How to use RTK query selector with an argument?
https://medium.com/nmc-techblog/rtk-query-best-practices-e0296d1679e6
How to call endpoint.select() in RTK query with an argument to retrieve cached data (within another selector)?
You should generally just use the useGetGroupAvailableAssessmentsQuery hook - you can combine that with selectFromResult.
const selectFiltered = createSelector(
result => result.data,
(_, assessmentId) => assessmentId,
(data, assessmentId) => {
return data.find((item) => item.id === assessmentId);
}
)
const result = useGetGroupAvailableAssessmentsQuery(something, {
selectFromResult(result) {
return { ...result, data: selectFiltered(result, assessmentId) }
}
})

useSwr fetch with filter query params

I have a component which displays a list of sources. Users will be able to filter this list with search keywords.
I have a state called searchKeyword which holds the input value.
I need to make a request when user clicks on search.
const { data, error } = useSWR(`source-requests/favorite?keyword=${searchKeyword}`);
const submitSearch = (e) => {
e.preventDefault();
mutate(source-requests/favorite?keyword=${searchKeyword});
};
This makes call on every keystroke. How do I prevent this.
I believe what you are looking for is a debounce function.
You could implement your own with something like this:
const debounce = (func, wait) => {
let timeout
return (...args) => {
clearTimeout(timeout)
timeout = setTimeout(() => {
func(args)
}, wait)
}
}
All together it could look something like this:
const [searchKeyword, setSearchKeyword] = useState(null)
const { data, error } = useSWR(
searchKeyword
? `source-requests/favorite?keyword=${searchKeyword}`
: null
);
const submitSearch = debounce(setSearchKeyword, 500);
Alternatively, you could use a more featureful debounce function from a library such as lodash or underscore.

correct way to use firestore onSnapShot with react redux

I'm trying to figure out the correct way to use firestore.onSnapshot with react-redux.
I currently have this code in my action file, which I am calling on componentWillMount() in my component.
export const fetchCheckins = () => async (dispatch) => {
const {currentUser} = firebaseService.auth();
try {
let timestamp = (new Date());
//set timestamp for beginning of today
timestamp.setHours(0);
//get checkins today
let checkinstoday = (await firebaseService.firestore().collection(`/checkins/${currentUser.uid}/log`).where("timestamp",">=",timestamp).orderBy("timestamp","desc").get()).docs.map(doc => doc.data());
//set timestamp for beggining of week
timestamp.setDate(-(timestamp.getDay()));
//get checkins (week)
let checkinsweek = (await firebaseService.firestore().collection(`/checkins/${currentUser.uid}/log`).where("timestamp",">=",timestamp).orderBy("timestamp","desc").get()).docs.map(doc => doc.data());
//set timestamp for begging of month
timestamp.setDate(0);
//get checkins (month)
let checkinsmonth = (await firebaseService.firestore().collection(`/checkins/${currentUser.uid}/log`).where("timestamp",">=",timestamp).orderBy("timestamp","desc").get()).docs.map(doc => doc.data());
dispatch({type: FETCH_CHECKINS, payload: { today: checkinstoday, week: checkinsweek, month: checkinsmonth}});
}
catch(e){
console.error(e);
}
};
this works fine, the correct data is sent to the component and display. The problem is, that if the user checks in, the checkin data should adjust, but it does not, since I am getting the data once and sending it, and the state is not re-rendering.
My question is how I should approach this? Do I use .onSnapshot() instead of .get()? Do I call .fetchCheckins() from the .checkin() action creator? How do I approach according to best practice? thank you
According to firestore's documentation if you need realtime updates you should use onSnapshot:
https://firebase.google.com/docs/firestore/query-data/listen
In your case if you use .get() - you get the update once and firestore won't notify you if any of the data changes. That's why you are not seeing the changes.
P.S. checkout redux-firestore: https://github.com/prescottprue/redux-firestore - it's nice library that can help you with your redux bindings.
You could subscribe your list like this:
function subscribeToExperiences() {
return eventChannel((emmiter: any) => {
experiencesRef.onSnapshot({ includeMetadataChanges: true }, snapshot => {
const experiences: IExperience[] = snapshot.docChanges().map(change => ({
id: change.doc.id,
title: change.doc.data().title
}));
if (snapshot.docChanges().length !== 0) {
emmiter(experiences);
}
});
return () => experiencesRef;
});
}
function* fetchExperiences(_: ExperiencesFetchRequested) {
const channel = yield call(subscribeToExperiences);
try {
while (true) {
const experiences = yield take(channel);
yield put(new ExperiencesFetchSucceeded(experiences));
}
} finally {
if (yield cancelled()) {
channel.close();
}
}
}
subscribeToExperiences uses a redux-saga eventChannel. An eventChannel receives an emmiter that generates a saga effect to be consumed with take. The eventChannel has to return a function to close the connections but afaik .onSnapshot connections don't need to be explicitly closed, that's why I return a dummy function.

Alternative to direct calling of store.dispatch()

Since in the latest redux-observable (0.17) is direct calling of store.dispatch() deprecated, I wonder what is an alternative if I need to dispatch actions from the outside of my redux app.
Example:
Let's say I have this function which initialize native module and set up native handler.
const configure = (dispatch) => {
const printingModule = NativeModules.PrintingManager
const eventEmitter = new NativeEventEmitter(printingModule)
eventEmitter.addListener(
"PrintingManagerNewPrinterConnected",
(payload) => dispatch({
type: PRINTER_MANAGER_NEW_PRINTER_CONNECTED,
payload: {
macAddress: payload[2],
connectionType: payload[3],
},
}))
printingModule.initialize()
}
What I typically do is that I will call this function from observable after something like APP_STARTUP_FINISHED:
const appStatePrepared = (action$: Object, { dispatch }) =>
action$.ofType(APP_STATE_PREPARED)
.switchMap(() => {
configurePrinters(dispatch)
})
What is the correct solution for this?
Thanks!
When using RxJS the ideal is to compose streams. So in this case we need to some how create a stream of "PrintingManagerNewPrinterConnected" events that we can then map each to their own PRINTER_MANAGER_NEW_PRINTER_CONNECTED action.a
Let's first learn how to do this completely custom.
Custom Observables
Creating your own custom Observables is very similar to creating a Promise. So say you had the most simple Promise in the world that just immediately resolves to the number 1
const items = new Promise(resolve => {
resolve(1);
});
The equivalent Observable looks super similar
const items = new Observable(observer => {
observer.next(1);
observer.complete();
});
Visually, the main differences are that instead of being passed (resolve, reject) callbacks we're given an Observer because there is next, error, and complete.
Semantically, Observables can represent more than one value by calling observer.next as many times as they'd like until they call observer.complete() to signal the end of the stream; this is in contrast to Promises, which only represent a single value.
Observables are also lazy and synchronous by default, whereas Promises are always eager and async.
Now that we have that understanding we want to take that and wrap your NativeEventEmitter API which uses addEventListener.
const configurePrinters = () => {
return new Observable(observer => {
const printingModule = NativeModules.PrintingManager;
const eventEmitter = new NativeEventEmitter(printingModule);
eventEmitter.addListener(
'PrintingManagerNewPrinterConnected',
(payload) => observer.next(payload)
);
printingModule.initialize();
});
};
configurePrinters()
.subscribe(payload => console.log(payload));
This works and is super simple, but there's one problem with it: we should call removeListener when they unsubscribe so that we clean up after ourselves and don't leak memory.
To do that, we need to return a subscription inside our custom Observable. A subscription in this context is an object that has an unsubscribe() method on it, which will be called automatically when the subscriber unsubscribes, an error is triggered, or your observable completes. This is your chance to clean up.
const items = new Observable(observer => {
let i = 0;
const timer = setInterval(() => {
observer.next(i++);
}, 1000);
// return a subscription that has our timer cleanup logic
return {
unsubscribe: () => {
clearInterval(timer);
}
};
});
Because returning an object is a bit verbose RxJS supports a shorthand where you just return a function which itself will be treated as the unsubscribe method.
const items = new Observable(observer => {
let i = 0;
const timer = setInterval(() => {
observer.next(i++);
}, 1000);
// return an "unsubscribe" function that has our timer cleanup logic
return () => {
clearInterval(timer);
};
});
Now we can apply this to our example, where we want to remove our listener when our unsubscribe teardown function is called.
const configurePrinters = () => {
return new Observable(observer => {
const printingModule = NativeModules.PrintingManager;
const eventEmitter = new NativeEventEmitter(printingModule);
const listener = (payload) => observer.next(payload);
eventEmitter.addListener(
'PrintingManagerNewPrinterConnected',
listener
);
printingModule.initialize();
return () => eventEmitter.removeListener(
'PrintingManagerNewPrinterConnected',
listener
);
});
};
Now let's turn this into a reusable utility function
const fromPrinterEvent = (eventName) => {
return new Observable(observer => {
const printingModule = NativeModules.PrintingManager;
const eventEmitter = new NativeEventEmitter(printingModule);
const listener = (payload) => observer.next(payload);
eventEmitter.addListener(eventName, listener);
printingModule.initialize();
return () => eventEmitter.removeListener(eventName, listener);
});
};
fromPrinterEvent('PrintingManagerNewPrinterConnected')
.subscribe(payload => console.log(payload));
Observable.fromEvent
While NativeEventEmitter is a react-native thing, it follows the node-style EventEmitter interface and RxJS already comes with a utility helper to create an Observable from them to save you the effort. It's called fromEvent, found at Observable.fromEvent or import { fromEvent } from 'rxjs/observables/fromEvent'.
const fromPrinterEvent = (eventName) => {
return Observable.defer(() => {
const printingModule = NativeModules.PrintingManager;
const eventEmitter = new NativeEventEmitter(printingModule);
printingModule.initialize();
return Observable.fromEvent(eventEmitter, eventName);
});
};
Here I also wrapped it in Observable.defer so that we don't create the NativeEventEmitter or printingModule.initialize() until someone actually subscribes (maintain laziness). This may or may not be neccesary for you, I don't know what PrintingManager does or how it behaves. e.g. it might be desirable to only create a single emitter and to initialize the module upfront.
const printingModule = NativeModules.PrintingManager;
const printerEmitter = new NativeEventEmitter(printingModule);
printingModule.initialize();
const fromPrinterEvent = (eventName) =>
Observable.fromEvent(printerEmitter, eventName);
So keep in mind that I'm just showing patterns without knowing what PrintingManager, etc does.
Use it within redux-observable
To use this within redux-observable and your epic is now the same as you'd use any other Observable. So we'll want to map the values from it to actions and
mergeMap, switchMap, concatMap, or exhaustMap that into our top-level stream.
Something like this:
const appStatePrepared = action$ =>
action$.ofType(APP_STATE_PREPARED)
.switchMap(() =>
fromPrinterEvent('PrintingManagerNewPrinterConnected')
.map(payload => ({
type: PRINTER_MANAGER_NEW_PRINTER_CONNECTED,
payload: {
macAddress: payload[2],
connectionType: payload[3],
}
}))
);
Remember that many streams, including our custom fromPrinterEvent('PrintingManagerNewPrinterConnected'), go forever until you unsubscribe from them. So if you only want one you'd use .take(1). If you want to unsubscribe when you receive another action you'd use .takeUntil(action$.ofType(WHATEVER)), etc. Normal RxJS patterns.

Assigning Field Value of Firestore Document to Const on Cloud Functions

I would like to assign the value of a field from a document to a constant to use it across several functions.
const stripeAccountId = firestore.doc('orgs/' + subscription.orgId).get()
.then( org => {
return org.data().stripeAccountId
})
The firestore.doc('orgs/' + subscription.orgId).get().then(...) method returns a promise. More info: https://scotch.io/tutorials/javascript-promises-for-dummies
Promises are async and you need to assign the stripeAccountId inside the arrow function specified inside the then.
I don't know where you will use it, but the stripeAccountId will only be filled after the promise is resolved.
const stripeAccountId = null;
firestore.doc('orgs/' + subscription.orgId).get().then(org => {
stripeAccountId = org.data().stripeAccountId;
})
console.log(stripeAccountId); // null
const sufficientTimeInMillisToResolveThePromise = 10000;
setTimeout(() => {
console.log(stripeAccountId); // some-id
}, sufficientTimeInMillisToResolveThePromise);

Resources