Error in epic with redux-observable and rxjs - firebase

I'm trying to create an epic that fetches data from firestore using rxfire. On every emission of a value, an action needs to be dispatched. My codes looks like this:
const fetchMenu = (action$, _state$) => {
return action$.pipe(
ofType(Types.FETCH_MENU_REQUESTED),
flatMap((async ({ resID }) => {
const firestore = firebase.firestore();
const menuRef = firestore.collection('menus').where('resID', '==', resID);
return collectionData(menuRef, 'id')
.pipe(
map(val => {
return Creators.fetchMenuSuccess(val);
})
)
}
)),
);
};
However, I'm getting the error Actions must be plain objects. Use custom middleware for async actions.
As far as I understand, the pipe operator is wrapping my value in an observable and that's why I'm getting the error, but I'm not sure what to do so that it only returns the action. I'm still a bit new to rxjs so any help would be very appreciated.

Your flatMap callback is async, which means it's not returning an observable that emits the fetchMenuSuccess action, it's returning a promise that resolves to the observable. Rx will unwrap the promise automatically, which means that the observable returned from this epic emits observables instead of subscribing to them and emitting their own values.
Removing the async keyword should fix it.

Related

Redux, Redux Toolkit how to wait until dispatch is finished

guys
Like in the subject is there a solution to wait until dispatch action is finished and then dispatch another?
Do I need thunk?
dispatch(someAction());
when someAction is finished dispatch anotherAction()
dispatch(anotherAction());
It really depends on the action. Normal non-thunk actions are synchronous, so in the next line after the dispatch, the first action will already be 100% handled.
If you are dispatching thunk actions there, you can either await or .then(..) the value that is returned by dispatch.
To elaborate on the idea of #phry.
I use redux-toolkit and have this as an example thunk:
export const EXAMPLE_THUNK = createAsyncThunk(
"group/event/example_thunk",
async () => {
// Just resolves after 2 seconds.
return await new Promise((resolve) => setTimeout(resolve, 2000));
}
);
To dispatch another action after the first dispatch, you can simply await the first dispatch. In my example I updated the loading state.
Although this example works, I am not 100% sure this is the best design pattern out there. Many examples I have seen is updating the state is some dispatch has been resolved! Personally I think this is a more clear way of writing my code
const handleChangeStatus = async () => {
setIsLoading(true);
await dispatch(EXAMPLE_THUNK())
// Or dispatch something else.
setIsLoading(false);
};

How to unsubscribe from Firestore realtime listener which is wrapped in an rxjs observable?

I have written a function that returns an observable that wraps the firestore onSnapshot method.
function getObservable() {
return Observable.create(observer => {
firebase.firestore().collection('users').onSnapshot(
snapshot => observer.next(snapshot.docs),
);
});
})
}
I am able to use this function and get updated docs as follows
const observable = getObservable()
const subscription = observable.subscribe(users => console.log('users'));
If I now call the subscription.unsubscribe() method, I will unsubscribe from the subscription. However, I will not subscribe from the onSnapshot listener.
Is there any way such that when I unsubscribe from the observable, I will automatically unsubscribe from the onSnapshot method.
From RxJS documentation (http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#static-method-create):
onSubscription can optionally return either a function or an object
with unsubscribe method. In both cases function or method will be
called when subscription to Observable is being cancelled and should
be used to clean up all resources. So, for example, if you are using
setTimeout in your custom Observable, when someone unsubscribes, you
can clear planned timeout, so that it does not fire needlessly and
browser (or other environment) does not waste computing power on
timing event that no one will listen to anyways.
So, if I have understood this right, you may want to try something similar to this
function getObservable() {
return Observable.create(observer => {
const unsubscribe = firebase.firestore().collection('users').onSnapshot(
snapshot => observer.next(snapshot.docs),
);
return unsubscribe;
});
})
}

redux-observable dispatch actions

I need to dispatch some actions in some order using redux-observable however, it takes just last action to dispatch. Please see example:
export const fetchClientsEpic = (action$, { dispatch }) =>
action$
.ofType(fetchClients)
.mapTo(fetchClientsPending(true))
.mergeMap(() => {
return ajax
.getJSON('some/get/clients/api')
.map((clients: IClient[]) => {
return fetchClientsSuccess(
map(clients, (client, index) => ({
key: index,
...client,
})),
);
});
});
fetchClientsSuccess is dispatched with clients but fetchClientsPending not, I totally do not get it why. I could use dispatch because I get it in params, but I feel it is not good solution(?). It should be done in the stream I guess. I am starting with RxJs and redux-observable. Is it possible to do?
Operators are chains of Observables where the input of one stream is the output of another. So when you use mapTo you're mapping one action to the other. But then your mergeMap maps that Pending action and maps it to that other inner Observable that does the ajax and such, effectively throwing the Pending action away. So think of RxJS as a series of pipes where data flows through (a stream)
While there is no silver bullet, in this particular case what you want to achieve can be done by using startWith at the end of your inner Observable
export const fetchClientsEpic = (action$, { dispatch }) =>
action$
.ofType(fetchClients)
.mergeMap(() => {
return ajax
.getJSON('some/get/clients/api')
.map((clients: IClient[]) => {
return fetchClientsSuccess(
map(clients, (client, index) => ({
key: index,
...client,
})),
);
})
.startWith(fetchClientsPending(true)); // <------- like so
});
This is in fact the same thing as using concat with of(action) first, just shorthand.
export const fetchClientsEpic = (action$, { dispatch }) =>
action$
.ofType(fetchClients)
.mergeMap(() => {
return Observable.concat(
Observable.of(fetchClientsPending(true)),
ajax
.getJSON('some/get/clients/api')
.map((clients: IClient[]) => {
return fetchClientsSuccess(
map(clients, (client, index) => ({
key: index,
...client,
})),
);
})
);
});
That said, I would recommend against synchronously dispatching another action to set the state that fetching is pending and instead rely on the original fetchClients action itself for the same effect. It should be assumed by your reducers that if such an action is seen, that some how the fetching still start regardless. This saves you the boilerplate and helps a bit on micro-perf since you don't need to run through the reducers, epics, and rerender twice.
There's no rules though, so if you feel strongly about this, go for it :)

Redux - Jest: Testing functions that have void return

New to Jest and Redux and I'm having trouble with testing functions that are dispatching to the store but don't yield a return value. I'm trying to follow the example from the Redux website does this
return store.dispatch(actions.fetchTodos()).then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
however I have several "fetchtodos" functions that don't return anything which causes the error TypeError:
Cannot read property 'then' of undefined due to returning undefined
I'm wondering what I can do to test that my mock store is correctly updating. Is there a way to dispatch the function, wait for it to finish and then compare the mock store with expected results?
Thanks
Edit: We're using typescript
action from tsx
export function selectTopic(topic: Topic | undefined): (dispatch: Redux.Dispatch<TopicState>) => void {
return (dispatch: Redux.Dispatch<TopicState>): void => {
dispatch({
type: SELECT_Topic,
payload: topic,
});
dispatch(reset(topic));
};
}
test.tsx
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('Select Topic action', () => {
it('should create an action to select .', () => {
const topic: Topic = mockdata.example[0];
const expectedAction = {
type: actions.SELECT_TOPIC,
payload: topic,
};
const store = mockStore(mockdata.defaultState);
return store.dispatch(actions.selectTopic(topic)).then(() => {
expect(store.getState()).toEqual(expectedAction);
});
});
});
The action is what I'm given to test(and there are many other functions similar to it. I'm getting that undefined error when running the test code, as the function isn't returning anything.
In Redux, the store's dispatch method is synchronous unless you attach middleware that changes that behavior, ie: returns a promise.
So this is likely a redux configuration problem. Be sure you are setting up your test store with the same middleware that allows you to use the promise pattern in production.
And as always, be sure to mock any network requests to avoid making api calls in test.

async/await with firebase .on() database listener

I'm designing a messaging application with redux as my state manager and firebase to store my data. I've started writing my database listeners in this fashion:
const fetchMessages = roomKey => async dispatch => {
const db = firebase.database();
let { messages } = await db.ref(`messages/${roomKey}`).on('value');
dispatch({
type: SET_MESSAGES,
payload: messages,
})
};
All this basically does is fetch messages by a room key and then dispatch an action that sets the messages in the redux state.
Traditionally, this would be written as such:
db.ref(`messages/${roomKey}`).on('value', snapshot => {
const messages = snapshot.messages;
dispatch({
type: SET_MESSAGES,
payload: messages,
})
});
And everytime something changes in messages/${roomKey}, my dispatch function would be executed. I'm wondering if this will work the same using the async await syntax, and if not, how I could make it work.
Hope this was enough detail!
The reference's on method does not return a promise. The callback it's passed can be invoked multiple times, so a promise does not fit with the method's contract.
However, the reference's once method method does return a promise, as the (optional) callback it's passed is invoked only once - after which the promise resolves. The once method is likely the one you want to use.

Resources