Redux saga yield call not waiting with firebase? - reactjs

I create a saga to update user information. But yield console.log('waiting'); not waiting util my Fibase update complete
export function* handleUpdateUserInfo(action) {
yield put(updateUserInfoPending());
const { userInfo, userID } = action;
const currentUser = yield select(state => state.user.currentUser);
const newUser = {
...currentUser,
...userInfo
};
try {
yield call(() => updateUserProfile(newUser, userID));
yield console.log('waiting');
} catch (error) {
yield put(updateUserInfoFailure(error));
}
}
Firebase
export const updateUserProfile = (newUser, userID) => {
try {
firestore
.collection('users')
.doc(userID)
.set(newUser);
} catch (error) {
throw error;
}
};
I want to ask how I fix it? and why it happen

If you return the call from updateUserProfile, you can await its result in the caller.
export const updateUserProfile = async (newUser, userID) => {
return firestore
.collection('users')
.doc(userID)
.set(newUser);
};
Then you call it with:
yield call(() => await updateUserProfile(newUser, userID));
yield console.log('waiting');

Related

How To Know If Dispatch Is Success or No In Redux Saga

So i already create the createMonsterStart and it will hit my API, my API is returning response code, and I want to alert success when the response code is 00 otherwise it will alert failed, how can i achieve that? here is my code:
const onSubmitHandler = () => {
dispatch(createMonsterStart(monster))
if(dispatch success){
alert("success")
}else{
alert("error")
}
}
And here is the redux saga code:
export function* createMonsterAsync({ payload: { monster } }) {
try {
const user = yield select(getUser)
const a = yield call(createMonster, user.user.token, monster)
if (a.error) {
yield put(createMonsterFailure(a.error))
return false
}
const monsters = yield call(fetchMonsterAsync)
yield put(createMonsterSuccess(monsters))
} catch (error) {
yield put(createMonsterFailure(error))
}
}

I need to cancel a particular task in redux saga from the tasks which r running parallely. with the code below all of the parallel tasks are cancelled

function* imageUploadfunctionCall(payload) {
for (let image of payload.payload) {
const {response, error} = yield call(imageUploadRequest(image))
if (response) {
yield put({type: ON_UPLOAD_SUCCESS, payload: image})
} else if (error) {
console.log('error', error)
}
}
}
export function* watchImageUpload() {
while (true) {
let workerTask = yield takeEvery(
ON_UPLOAD_PROGRESS,
imageUploadfunctionCall
)
yield take(ON_CANCEL_BATCH_UPLOAD)
yield cancel(workerTask)
}
}
There is multiple ways you can do this, for example you can use an in-between saga with a race effect:
function* imageUploadfunctionCall(payload) {
for (let image of payload.payload) {
const {response, error} = yield call(imageUploadRequest(image))
if (response) {
yield put({type: ON_UPLOAD_SUCCESS, payload: image})
} else if (error) {
console.log('error', error)
}
}
}
function* imageUploadSaga(payload) {
yield race([
call(imageUploadfunctionCall, payload),
take(a => a.type === ON_CANCEL_BATCH_UPLOAD && a.id === payload.id),
])
}
export function* watchImageUpload() {
yield takeEvery(ON_UPLOAD_PROGRESS, imageUploadSaga)
}
The code above assumes that you send an id property for both the ON_UPLOAD_PROGRESS & ON_CANCEL_BATCH_UPLOAD actions so you can identify which one to cancel.
On a side note, in the upload saga you have:
yield call(imageUploadRequest(image))
which should be probably instead
yield call(imageUploadRequest, image)
(unless imageUploadRequest is a function factory).
For more complex cases you could hold a map of tasks & ids.
export function* watchImageUpload() {
const taskMap = {}
yield takeEvery(ON_CANCEL_BATCH_UPLOAD, function* (action) {
if (!taskMap[action.payload]) return
yield cancel(taskMap[action.payload])
delete taskMap[action.payload]
})
while (true) {
let payload = yield take(ON_UPLOAD_PROGRESS, imageUploadSaga)
const workerTask = yield fork(imageUploadfunctionCall, payload)
taskMap[payload.id] = workerTask
}
}
Again, you need some id in both actions.

Use eventChannel to get real time data from Firestore

I would like to sync my data in React with Firestore, each time some data is changed I would like to have an update/refresh of my data.
For fetching the data I have:
function* syncCustomers() {
try {
const response = yield call(fireBaseBackend.getCustomersAPI);
yield put(loadCustomers(response));
} catch (error) {
yield put(customerError(error));
}
}
Where my getCustomersAPI is:
getCustomersAPI = () => {
return new Promise((resolve, reject) => {
firebase.firestore().collection("customers").onSnapshot((snap) => {
let documents = [];
snap.docs.forEach(doc => {
let result = doc.data();
result.uid = doc.id;
documents.push(result);
});
resolve(documents);
});
});
};
This works to get my data, for the real time I implemented:
function* usersChannelSaga () {
try {
while (true) {
const response = yield call(fireBaseBackend.getCustomersAPI);
yield put(loadCustomers(response));
}
}
catch (error) {
yield put(customerError(error));
}
}
But I would like to use the eventChannel for this. How does this need to be implemented?
I tried:
function* usersChannelSaga () {
const channel = eventChannel(emit => fireBaseBackend.syncCustomers(emit));
try {
while (true) {
const data = yield take(channel);
yield put(loadCustomers(response));
}
}
catch (error) {
yield put(customerError(error));
}
}
Where syncCustomers is :
syncCustomers(emit){
return firebase.firestore().collection("customers").onSnapshot(emit);
}
Anyone an idea how to solve this?

Failing to integrate Redux-saga with Redux

I am trying to use Redux-saga for the first time, so I have the following sagas file:
backendAuth.js
import {all, call, fork, put, takeEvery} from "redux-saga/effects";
import {auth} from 'backend/backend';
import {
SIGNIN_USER,
SIGNOUT_USER,
SIGNUP_USER
} from "constants/ActionTypes";
import {showAuthMessage, userSignInSuccess, userSignOutSuccess, userSignUpSuccess} from "actions/Auth";
const createUserWithUsernamePasswordRequest = async (username, password) =>
await auth.createUserWithUsernameAndPassword(username, password)
.then(authUser => {
console.log('authUser: '+authUser);
return authUser;
})
.catch(error => error);
const signInUserWithUsernamePasswordRequest = async (username, password) =>
await auth.signInWithUsernameAndPassword(username, password)
.then(authUser => authUser)
.catch(error => error);
const signOutRequest = async () =>
await auth.signOut()
.then(authUser => authUser)
.catch(error => error);
function* createUserWithUsernamePassword({payload}) {
const {username, email, password} = payload;
try {
const signUpUser = yield call(createUserWithUsernamePasswordRequest, username, email, password);
if (signUpUser.message) {
yield put(showAuthMessage(signUpUser.message));
} else {
localStorage.setItem('user_id', signUpUser.user.uid);
yield put(userSignUpSuccess(signUpUser.user.uid));
}
} catch (error) {
yield put(showAuthMessage(error));
}
}
function* signInUserWithUsernamePassword({payload}) {
const {username, password} = payload;
try {
const signInUser = yield call(signInUserWithUsernamePasswordRequest, username, password);
if (signInUser.message) {
yield put(showAuthMessage(signInUser.message));
} else {
localStorage.setItem('user_id', signInUser.user.uid);
yield put(userSignInSuccess(signInUser.user.uid));
}
} catch (error) {
yield put(showAuthMessage(error));
}
}
function* signOut() {
try {
const signOutUser = yield call(signOutRequest);
if (signOutUser === undefined) {
localStorage.removeItem('user_id');
yield put(userSignOutSuccess(signOutUser));
} else {
yield put(showAuthMessage(signOutUser.message));
}
} catch (error) {
yield put(showAuthMessage(error));
}
}
export function* createUserAccount() {
yield takeEvery(SIGNUP_USER, createUserWithUsernamePassword);
}
export function* signInUser() {
yield takeEvery(SIGNIN_USER, signInUserWithUsernamePassword);
}
export function* signOutUser() {
yield takeEvery(SIGNOUT_USER, signOut);
}
export default function* rootSaga() {
yield all([
fork(signInUser),
fork(createUserAccount),
fork(signOutUser)
]);
}
And this is the file where the asynchronous consult to an api is performed:
backend.js
import axios from 'axios';
const backendServer = 'http://localhost:8000/';
const signInEndpoint = backendServer + 'api/token_auth/';
const signInWithUsernameAndPassword = (username, password) => {
axios.post(backendServer+"api/token_auth/", {
username: username,
password: password
})
.then(Response => {
console.log('Response: '+Response)
return Response;
})
.catch(Error => Error);
}
export const auth = {
signInWithUsernameAndPassword: signInWithUsernameAndPassword
}
The ajax is well executed through axios, and the console.log() in backend.js is reached, but the console.log() in backendAuth is not, and I get the following error in the console:
index.js:1375 Invariant Violation: Objects are not valid as a React child (found: TypeError: Cannot read property 'then' of undefined). If you meant to render a collection of children, use an array instead.
I believe the problem lies in the way I am defining the return of the value of the then in the ajax of backend.js, but I am pretty new to frontend development, so I am not sure about it.
The root cause is a simple fix - you are using an explicit block for the body of your signInWithUsernameAndPassword arrow function, so you need to use an explicit return. Since you have no return statement that function returns undefined which is cascading to cause the overall issue.
The quick fix is just to update the function to:
const signInWithUsernameAndPassword = (username, password) => {
return axios.post(backendServer+"api/token_auth/", {
username: username,
password: password
})
.then(Response => {
console.log('Response: '+Response)
return Response;
})
.catch(Error => Error);
}
Now to understand the final error you're seeing due at the moment: since signInWithUsernameAndPassword returns undefined, you eventually enter the catch block of signInWithUsernamePassword in backendAuth.js due to the type error of calling undefined.then in signInUserWithUsernamePasswordRequest. The error in that catch block is an Error instance, not a plain string. I'm assume you're then displaying the error that is passed to showAuthError somewhere in a react component tree, but an Error instance is not something that is valid to be rendered. You could try calling toString() on it first to display future errors instead of causing a rendering error.

How to chain firebase observables using redux-saga?

I've recently made the move from ionic/angular2/Rxjs to React/React Native/Redux-sagas. I'm porting over my app from ionic to react native and have really enjoyed the process. One thing, however, I have really struggled with is using firebase in react native via redux sagas. I can do simple requests however I would like to chain three requests and get the return value as an JSON object. Currently I have this :
export const getTuteeUID = state => state.login.username;
function* getMemberProfiles(UID) {
try {
const memberObject = yield call(get, 'userProfile', UID);
return memberObject;
} catch (err) {
console.log(err);
}
}
function* getLatestConversation(grpID) {
try {
const opts = newOpts();
const refLatestConvo = firebase
.database()
.ref('conversations')
.child(`${grpID}`)
.orderByChild('createdAt')
.limitToLast(1);
yield call([refLatestConvo, refLatestConvo.on], 'value', opts.handler);
while (true) {
const { data } = yield take(opts);
if (data.val()) {
return data.val()[data._childKeys[0]];
}
return null;
}
} catch (err) {
console.log(err);
}
}
export function* getChatGroupObject(grpID, tuteeUID) {
try {
const groupObject = yield call(get, 'groups', grpID);
const memberProfiles = yield Object.keys(groupObject.members)
.filter(mem => mem !== tuteeUID)
.map(memUID => call(getMemberProfiles, memUID));
const latestConversation = yield call(getLatestConversation, grpID);
return { ...groupObject, memberProfiles, key: grpID, latestConversation };
} catch (err) {
console.log(err);
}
}
/**
*
* #return {Generator} []
*/
export function* fetchChatGroups() {
try {
const opts = newOpts();
const tuteeUID = yield select(getTuteeUID);
const refGrpIDs = firebase.database().ref('userProfile').child(`${tuteeUID}/groups`);
const snapGrpIDs = yield call([refGrpIDs, refGrpIDs.once], 'value', opts.handler);
if (snapGrpIDs.val()) {
const groupObject = yield Object.keys(snapGrpIDs.val()).map(grpID =>
call(getChatGroupObject, grpID, tuteeUID),
);
yield put(ChatAction.chatGroupsReceived(groupObject));
} else {
ChatAction.chatGroupsReceived([]);
}
} catch (error) {
console.log(error);
}
}
Now this works and returns the correct object, however if the latest conversation in the array changes the object won't update. How can I get this to continue updating? Another thing is if I were to put this in a while(true) loop, is there a way to unsubscribe from the observable? In rxjs this used to be super easy to do.

Resources