redux-observable epic with multiple filters - redux-observable

I am writing an epic using redux-observable and am trying to write an epic using multiple filters (oftype). Given below is my sample code
export const landingEpic = action$ => {
console.log('inside landing epic');
return action$.ofType('APPLY_SHOPPING_LISTS').map(() => (
{
type: 'APPLYING_SHOPPING_LISTS',
})
);
return action$.ofType('APPLIED_SHOPPING_LIST'){
//here I want to return something else
}
}
However I cannot have two return methods in one epic?

You'll want to combine them with Observable.merge() then return that--however I also highly suggest separating them into two separate epics. It will make it easier to test, but that's of course your call.
export const landingEpic = action$ => {
return Observable.merge(
action$.ofType('APPLY_SHOPPING_LISTS')
.map(() => ({
type: 'APPLYING_SHOPPING_LISTS',
}),
action$.ofType('APPLIED_SHOPPING_LIST')
.map(() => ({
type: 'SOMETHING_ELSE',
}),
);
}

It sounds like you want to use combineEpics:
import { combineEpics } from "redux-observable";
const landingEpic1 = // put epic1 definition here
const landingEpic2 = // put epic2 definition here
export default combineEpics(
landingEpic1,
landingEpic2,
// ...
);

Related

Fetch more data in RxJs

I have some problem with apply fetching "more" data using fromFetch from rxjs.
I have project with React and RXJS. Currently I'm using something like this:
const stream$ = fromFetch('https://pokeapi.co/api/v2/pokemon?limit=100', {
selector: response => response.json()
}).subscribe(data => console.log(data));
But! I would like to change limit dynamically, when I click button or even better - when I scroll to the very bottom of my website. How to make something like this?
So that, based on some interaction, the limit would change?
The way your observable work in your case it's a request-response. You're declaring stream$ to be an observable that when someone subscribes it will make a request with limit=100.
There are different ways of solving this... The most straightforward would be:
const getPokemon$ = limit =>
fromFetch('https://pokeapi.co/api/v2/pokemon?limit=' + limit, {
selector: response => response.json()
});
const MyComponent = () => {
// ...
useEffect(() => {
const sub = getPokemon$(limit).subscribe(res => console.log(res));
return () => sub.unsubscribe();
}, [limit])
// ...
}
Another option, probably a bit more reactive but harder to follow for others, would be to declare another stream which sets the limit:
const limit$ = new BehaviorSubject(100)
const pokemon$ = limit$.pipe(
switchMap(limit => fromFetch('https://pokeapi.co/api/v2/pokemon?limit=' + limit, {
selector: response => response.json()
}))
);
// In your component
const MyComponent = () => {
// ...
useEffect(() => {
const sub = pokemon$.subscribe(res => console.log(res));
return () => sub.unsubscribe();
}, [])
changeLimit = (newLimit) => limit$.next(newLimit)
// ...
}
In this other solution, you're declaring how pokemon$ should react to changes on limit$, and you can set limit$ from any other component you want.

Make two actions work simultaneously - react native and redux

I've a button that sends two actions. First one adds the user infos in an array if certain condition is met and 2nd one sends the data to the server.
Since both actions are in onPress function, the 2nd action doesn't wait till it adds up the infos in an array. Henceforth, it always sends empty array.
How can I make this two actions work simultaneously.
<TouchableOpacity
onPress={() => {
if (true) {
this.props.AuthUserInfoGet(SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo); //calculates & return SignUpUsers
}
this.props.SignUpCheck(SignUpUsers); //upload SignUpUsers but SignUpCheck is always empty here
}}
>
<Text>Upload</Text>
</TouchableOpacity>
const mapStateToProps = (state) => {
const {SignUpUsers} = state.Auth;
//it gives an empty array first and then expected value
console.log('SignUpUsersz', SignUpUsers);
return {SignUpUsers};
};
Action:
export const AuthUserInfoGet = (SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo) => {
return ({
type: SIGN_UP_USER_INFO_GET,
payloadName: SignUpName,
payloadDesignation: SignUpDesignation,
payloadEmail: SignUpEmail,
payloadMobile: SignUpMobileNo,
});
}
export const SignUpCheck = (userInfo) => {
console.log('userInfo', userInfo); // userInfo is always empty
}
Reducer:
const INITIAL_STATE = { SignUpUsers: [] }
case SIGN_UP_USER_INFO_GET:
return { ...state, SignUpUsers: [...state.SignUpUsers, {member_name: actions.payloadName, designation: actions.payloadDesignation,
email: actions.payloadEmail, mobile_number: actions.payloadMobile}] };
Given your current Redux-structure, I think what makes the most sense to use the componentDidUpdate life-cycle method.
The main reason is because your component ultimately needs to get updated data from Redux via props and needs to re-render. When you execute the first action, that user-data coming from the API is not immediately available in the current call-stack, so you'll always be passing an empty array (given your initial value of SignUpUsers: [])
Note that most React-Redux flows follow this path:
User-Event -> Action-Creator -> API (Data) -> Redux -> Component
Your click-event is at step 1 and triggers this action: this.props.AuthUserInfoGet(...args)
But React/Redux needs to go through that entire flow before you can use the new data.
This is where the componentDidUpdate() event comes in-handy because you can write logic when the component is re-rendered by new props or state.
Something like this would totally work:
componentDidUpdate(prevProps){
if(prevProps.SignUpUsers.length !== this.props.SignUpUsers.length){
//execute action
this.props.SignUpCheck(this.props.SignUpUsers)
}
}
For that I would suggest you take a look at redux-thunk middleware.
Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.
And based on your example, the code will end up like this:
<TouchableOpacity
onPress={() => this.props.uploadSignUpUsers(SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo)}>
<Text>Upload</Text>
</TouchableOpacity>
const mapStateToProps = (state) => {
const { Auth: { SignUpUsers } } = state;
return { SignUpUsers };
}
Actions:
export const SIGN_UP_GET_USER_INFO_SUCCESS = "SIGN_UP_GET_USER_INFO_SUCCESS";
export const SIGN_UP_UPLOAD_SUCCESS = "SIGN_UP_UPLOAD_SUCCESS";
export const uploadSignUpUsers = (SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo) => {
return async (dispatch, getState) => {
// here you can make the api call or any other async calculations
const { data: AuthUserInfo, error } = await api.post(SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo);
dispatch({
type: SIGN_UP_GET_USER_INFO_SUCCESS,
payloadName: AuthUserInfo.SignUpName,
payloadDesignation: AuthUserInfo.SignUpDesignation,
payloadEmail: AuthUserInfo.SignUpEmail,
payloadMobile: AuthUserInfo.SignUpMobileNo,
});
const { Auth: { SignUpUsers } } = getState()
// and now you can upload your SignUpUsers
const { data: uploadData, error } = await.api.post(SignUpUsers)
dispatch({
type: SIGN_UP_UPLOAD_SUCCESS,
...uploadData // spread upload data to make it available in reducers
});
}
}
Reducer:
const INITIAL_STATE = { SignUpUsers: [] }
case SIGN_UP_GET_USER_INFO_SUCCESS: {
const { payloadName, payloadDesignation, payloadEmail, payloadMobile } = actions
return {
...state,
SignUpUsers: [ ...state.SignUpUsers, {
member_name: payloadName,
designation: payloadDesignation,
email: payloadEmail,
mobile_number: payloadMobile
}]
}
}

RamdaJS - How can I use pipe with sync and async functions?

I'm trying to learn Ramda and how to use it on my daily work. So I have a quick question. "How can I use pipe with sync and async functions?" or best, how can I improve the following code?
const AuthService = () => ({
async signIn(credentials: Credentials): Promise<AuthSession> {
const result = await api.signIn(credentials)
return R.pipe(
signInResultToAuthSession,
saveAuthSession
)(result)
}
})
[EDITED]: A second alternative that I think be better.
const AuthService = () => ({
async signIn(credentials: Credentials): Promise<AuthSession> {
return api.signIn(credentials).then(
R.pipe(
signInResultToAuthSession,
saveAuthSession
)
)
}
})
pipeWith was added for just such cases. It wraps the functions in a common interface (here then) and passes the results along, just like pipe.
const api = {signIn: ({pwd, ...creds}) => Promise.resolve({...creds, signedIn: true})}
const signInResultToAuthSession = (creds) => Promise.resolve({...creds, auth: true})
const saveAuthSession = (creds) => Promise.resolve({...creds, saved: true})
const AuthService = {
signIn: pipeWith(then)([
api.signIn, // [1]
signInResultToAuthSession,
saveAuthSession
])
}
AuthService.signIn({name: 'fred', pwd: 'flintstone'})
.then(console.log)
// [1]: `bind` might be necessary here, depending on the design of `api.signIn`
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {pipeWith, then} = R </script>
Do note that pipeWith wraps the functions in an array. One day we'd like to do the same with pipe, but that's a hugely breaking change.
You could create a function like this:
const then = f => p => p.then(f)
And then your pipe would look like:
const AuthService = () => ({
async signIn(credentials: Credentials): Promise<AuthSession> {
return R.pipe(
api.signIn,
then(signInResultToAuthSession),
then(saveAuthSession),
)(credentials)
}
})
You could even catch exceptions with:
const pCatch = f => p => p.catch(f)
R.pipe(
api.signIn,
pCatch(err => console.error(err)),
then(signInResultToAuthSession),
then(saveAuthSession),
)(credentials)

Why is the parameter inside the action coming back as undefined when using redux?

Currently, I have a page which renders a list of dates, When a user presses a certain date, the user is then taken to a new page which renders the graph of the date that they pressed.
I want to use redux to update props, so that I can render a specific graph based on which button a user has pressed.
Inside my renderList() I return a mapped array that in turn returns a bunch of TouchableOpacities. Inside each TouchableOpacity, inside the onPress event, another function is called that passes all of the information about the test as a parameter. renderList looks like this.
let sorted = _.orderBy(this.props.testResults, testResult => testResult.created, 'desc');
moment.locale(localeToMomentLocale(I18n.locale));
return sorted.map((result, index) => {
let formattedDate = moment(result.created).format(I18n.t('report_header_dformat'));
let correctedDate = vsprintf(I18n.t('report_date_correction'), [formattedDate]);
let analysis = TestAnalysis.run(result);
return (
<TouchableOpacity
onPress={() => this.resultOrTest(result)}
style={styles.row} key={'_' + index}>
</TouchableOpacity>
resultOrTest looks like this:
resultOrTest = (result) => {
console.log('ReportDetailPage: resultOrTest: showing result: ', result.id);
this.props.setResultIdToProps(result.id);
this.props.navigation.navigate('ReportSinglePage');
};
mapDispatchToProps looks like this:
const mapDispatchToProps = (dispatch) => {
return {
setResultIdToProps: () => {
dispatch(setResultIdToProps());
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ReportDetailPage);
inside my actions/user.js page.
export const setResultIdToProps = (resultId) => {
// var newId = resultId.toString();
console.log('actions/user.js setResultIdToProps: resultid.......', resultId);
return (dispatch, getState) => {
dispatch({
type: SET_RESULT_ID_TO_PROPS,
resultId
});
}
};
Why does resultId keep coming back as undefined? Did I pass the wrong value/Parameter?
You need to properly pass the parameter to your action dispatcher in mapDispatchToProps. Right now, you're not passing the resultId, hence it is passed as undefined.
const mapDispatchToProps = (dispatch) => {
return {
setResultIdToProps: (resultId) => {
dispatch(setResultIdToProps(resultId));
}
}
}

redux-observable, How to do an operator like promise.all()?

I have two async requests, want to write a epic do the job like promise.all()
const fetchData1 = () => (action$: ActionsObservable<any>, store: any) => (
ajax.getJSON('../../mockData/promiseAll/data1.json').map((data: any) => {
return requestData1Success(data);
})
);
const fetchData2 = () => (action$: ActionsObservable<any>, store: any) => (
ajax.getJSON('../../mockData/promiseAll/data2.json').map((data: any) => {
return requestData2Success(data);
})
)
const requestAllDataEpic = (action$: ActionsObservable<any>, store: any) => {
return action$.ofType(t.REQUEST_ALL_DATA)
.map((action) => action.payload)
.switchMap((names: string[]) => {
console.log(names);
return Observable.forkJoin([
fetchData1()(action$, store),
fetchData2()(action$, store)
])
.map((results: any[]) => {
const [action1, action2] = results;
requestData1Success(action1);
requestData2Success(action2);
});
});
};
But when I dispatch the action, the console give me an error:
Uncaught TypeError: Cannot read property 'type' of undefined
I think the reason is I do not give the middleware an action object, but undefined.
How can I do this correctly?
In the provided example, you are not actually returning your two actions, you're returning nothing:
.map((results: any[]) => {
const [action1, action2] = results;
// v----- not returning either of these
requestData1Success(action1);
requestData2Success(action2);
});
map can't used to emit two actions sequentially because it's 1:1 not 1:many (mergeMap, switchMap, concatMap, etc are 1:many). However, in your example you are already converting the responses to the actions inside your fetchData helpers--doing it again would wrap an action inside another action, not what you want. This looks like a bug when you were refactoring.
Other than that, it's actually not clear what you intended to do. If you have further questions you'll need to describe what you want you'd like to achieve.