Passing store to switchMap function in observable epic - rxjs

I need to pass store value to switchMap function on epic but seems like only action is passed and store is 0.
export const addVideoEpic = (action$, store) =>
action$.ofType(videoTypes.ADD_VIDEO_REQUEST)
.filter(() => of(store.getState()))
.switchMap((action, store) => {
const data = {
url: store.url
};
console.log(store) // 0
return from(addVideo(data))
});
How to pass store value down to switchMap

In order to pass a store into the epic, your epic must be aware of the existence of a store. In your store creation class, please make sure that you have something like:
const epicMiddleware = createEpicMiddleware();
const persistedReducer = persistReducer(persistConfig, rootReducer as Reducer)
export const store = createStore(
persistedReducer,
applyMiddleware(epicMiddleware)
);
epicMiddleware.run(rootEpic);
And assuming that your version of redux-observable is ^1.0.0 or newer and rxjs is ^6.0.0 or newer, you can access the store and whatever state it stores as such:
export function fetchRandomStuffEpic(
action$: ActionsObservable<RandomAction>,
store$: StateObservable<RootState>
) {
return action$.pipe(
ofType(RANDOM),
switchMap(action => {
// store.getState() no longer works in the newer versions
// and has to accessed as a value property with store$
const { authToken } = store$.value.userData;
// whatever code that follows
})
);
}

Related

Subscribe two times to one observable

first func:
updateMark(item: MarkDTO) {
this.service
.put(item, this.resource)
.subscribe(() => this.markEdit = null);
}
second func:
put(item: MarkDTO, rcc: string): Observable<MarkDTO> {
const rdto = new MarkRDTO(item);
const url = `${this.getUrl('base')}${rcc}/marks/${rdto.rid}`;
const obs = this.http.put<MarkDTO>(url, rdto, { withCredentials: true })
.pipe(map((r: MarkDTO) => new MarkDTO(r)))
.share();
obs.subscribe(newMark => this.storage.update(newMark, rcc));
return obs;
}
in service i need to update data after request
but also in same time i need to clear current editItem
all of it must be done after i subscribe to one httpRequest
.share() - suport from rxjs-compat package (i want to remove this dep in closest time)
without .share() - work only 1 of 2 steps
current rxjs version is 6.3.3
Help who can...
There is a pipeable share operator, that you would use the same way you use map() (i.e. inside pipe()) and thus doesn't need rxjs-compat.
But you don't need share() here. All you need is the tap() operator:
put(item: MarkDTO, rcc: string): Observable<MarkDTO> {
const rdto = new MarkRDTO(item);
const url = `${this.getUrl('base')}${rcc}/marks/${rdto.rid}`;
return this.http.put<MarkDTO>(url, rdto, { withCredentials: true })
.pipe(
map(r => new MarkDTO(r)),
tap(newMark => this.storage.update(newMark, rcc))
);
}

Is reducer and global variable are the same?

Can I get the reducer state in Browser console like accessing declared javascript variables.?
You can use a middleware to store the redux state in a global variable.
import { createStore, applyMiddleware } from 'redux';
const stateInGlobal = store => next => action => {
const result = next(action);
window.reduxState = store.getState();
return result;
};
const store = createStore(reducer, undefined, applyMiddleware(stateInGlobal))
Now you can access to redux state through the variable reduxState.

How do items in an object from one function transfer into another function?

I am learning react redux, I am using firebase for storing data.
I installed thunk middleware. Everything works, I just don't understand why.
As I understand it, the const expense is an object which is in another function's scope. How can addExpense gets access to it?
export const addExpense = (expense) => ({
type: 'ADD_EXPENSE',
expense
});
export const startAddExpense = (expenseData = {}) => {
return (dispatch) => {
const {
description = '',
note = '',
amount = 0,
createdAt = 0
} = expenseData;
const expense = { description, note, amount, createdAt };
database.ref('expenses').push(expense).then((ref) => {
dispatch(addExpense({
id: ref.key,
...expense
}));
});
};
};
startAddExpense is passing the const expense object to your addExpense function, along with an id field. It just so happens that the argument to addExpense is also called expense, which is where you might be getting confused.
Hope that clears it up.

RxJs How to set default request headers?

Not sure is there any way to set default request headers in rxjs like we do with axios js as-
axios.defaults.headers.common['Authorization'] = 'c7b9392955ce63b38cf0901b7e523efbf7613001526117c79376122b7be2a9519d49c5ff5de1e217db93beae2f2033e9';
Here is my epic code where i want to set request headers -
export default function epicFetchProducts(action$, store) {
return action$.ofType(FETCH_PRODUCTS_REQUEST)
.mergeMap(action =>
ajax.get(`http://localhost/products?${action.q}`)
.map(response => doFetchProductsFulfilled(response))
);
}
Please help.
It's not possible to set default headers for all ajax requests using RxJS's ajax utilities.
You can however provide headers in each call, or create your own simple wrapper that provides them by default.
utils/ajax.js
const defaultHeaders = {
Authorization: 'c7b9392955ce63b38cf090...etc'
};
export const get = (url, headers) =>
ajax.get(url, Object.assign({}, defaultHeaders, headers));
my-example.js
import * as ajax from './utils/ajax';
// Usage is the same, but now with defaults
ajax.get(`http://localhost/products?${action.q}`;)
I'm using redux-observable but this applies to rxjs; maybe the next answer its too over-engineered, but I needed to get dinamically the headers depending of certain factors, without affecting the unit testing (something decoupled from my epics too), and without changing the sintax of ajax.get/ajax.post etc, this is what I found:
ES6 has proxies support, and after reading this and improving the solution here, I'm using a High Order Function to create a Proxy in the original rxjs/ajax object, and return the proxified object; below is my code:
Note: I'm using typescript, but you can port it to plain ES6.
AjaxUtils.ts
export interface AjaxGetHeadersFn {
(): Object;
}
// the function names we will proxy
const getHeadersPos = (ajaxMethod: string): number => {
switch (ajaxMethod) {
case 'get':
case 'getJSON':
case 'delete':
return 1;
case 'patch':
case 'post':
case 'put':
return 2;
default:
return -1;
}
};
export const ajaxProxy = (getHeadersFn: AjaxGetHeadersFn) =>
<TObject extends object>(obj: TObject): TObject => {
return new Proxy(obj, {
get(target: TObject, propKey: PropertyKey) {
const origProp = target[propKey];
const headersPos = getHeadersPos(propKey as string);
if (headersPos === -1 || typeof origProp !== 'function') {
return origProp;
}
return function (...args: Array<object>) {
args[headersPos] = { ...args[headersPos], ...getHeadersFn() };
// #ts-ignore
return origProp.apply(this, args);
};
}
});
};
You use it this way:
ConfigureAjax.ts
import { ajax as Ajax } from 'rxjs/ajax'; // you rename it
// this is the function to get the headers dynamically
// anything, a function, a service etc.
const getHeadersFn: AjaxGetHeadersFn = () => ({ 'Bearer': 'BLABLABLA' });
const ajax = ajaxProxy(getHeadersFn)(Ajax); // proxified object
export default ajax;
Anywhere in you application you import ajax from ConfigureAjax.ts and use it as normal.
If you are using redux-observable you configure epics this way (injecting ajax object as a dependency more info here):
ConfigureStore.ts
import ajax from './ConfigureAjax.ts'
const rootEpic = combineEpics(
fetchUserEpic
)({ ajax });
UserEpics.ts
// the same sintax ajax.getJSON, decoupled and
// under the covers with dynamically injected headers
const fetchUserEpic = (action$, state$, { ajax }) => action$.pipe(
ofType('FETCH_USER'),
mergeMap(({ payload }) => ajax.getJSON(`/api/users/${payload}`).pipe(
map(response => ({
type: 'FETCH_USER_FULFILLED',
payload: response
}))
)
);
Hope it helps people looking for the same :D

Redux Observable: How to return an action from a callback?

I'm using the WebRTC library which has a very specific API. The peerConnection.setRemoteDescription method's 2nd argument is supposed to be a callback for when it finishes setting the remote description:
This is one of my wrapper functions for my WebRTC class:
export function setRemoteSdp(peerConnection, sdp, callback) {
if (!sdp) return;
return peerConnection.setRemoteDescription(
new RTCSessionDescription(sdp),
callback, // <-------------
);
}
And this is a sketch of what I want to do:
function receivedSdp(action$, store) {
return action$.ofType(VideoStream.RECEIVED_SDP)
.mergeMap(action => {
const {peerConnection} = store.getState().videoStreams;
const {sdp} = action.payload;
return WebRTC.setRemoteSdp(peerConnection, sdp, () => {
return myReducer.myAction(); // <------ return action as the callback
})
})
};
This doesn't work since I'm not returning an Observable. Is there a way to do this?
P.S. this is the WebRTC API: https://github.com/oney/react-native-webrtc/blob/master/RTCPeerConnection.js#L176
martin's answer is correct about using Observable.create or new Observable--same thing (except it's not clear to me why you need the mergeAll() since the mergeMap will flatten?)
As a bonus, you could also use Observable.bindCallback for this.
// bindCallback is a factory factory, it creates a function that
// when called with any arguments will return an Observable that
// wraps setRemoteSdp, handling the callback portion for you.
// I'm using setRemoteSdp.bind(WebRTC) because I don't know
// if setRemoteSdp requires its calling context to be WebRTC
// so it's "just in case". It might not be needed.
const setRemoteSdpObservable = Observable.bindCallback(WebRTC.setRemoteSdp.bind(WebRTC));
setRemoteSdpObservable(peerConnection, sdp)
.subscribe(d => console.log(d));
Usage inside your epic would be something like this
// observables are lazy, so defining this outside of our epic
// is totally cool--it only sets up the factory
const setRemoteSdpObservable = Observable.bindCallback(WebRTC.setRemoteSdp.bind(WebRTC));
function receivedSdp(action$, store) {
return action$.ofType(VideoStream.RECEIVED_SDP)
.mergeMap(action => {
const {peerConnection} = store.getState().videoStreams;
const {sdp} = action.payload;
return setRemoteSdpObservable(peerConnection)
.map(result => myReducer.myAction());
})
};
You could use this to create Observable wrappers for all the WebRTC apis.
So the problem is that setRemoteSdp doesn't return an Observable while myReducer.myAction() does and that's the Observable you want to merge?
You can use Observable.create and wrap the WebRTC.setRemoteSdp call:
.mergeMap(action => {
return Observable.create(observer => {
WebRTC.setRemoteSdp(peerConnection, sdp, () => {
observer.next(myReducer.myAction());
observer.complete();
})
});
}
.mergeAll()
The Observable.create returns an Observable that emits another Observable from myReducer.myAction(). Now I have in fact so-called higher-order that I want to flatten using mergeAll() (concatAll would work as well).

Resources