dependabot[bot] on npm_and_yarn
chore(deps-dev): bump npm from … (compare)
dependabot[bot] on npm_and_yarn
jayphelps on master
chore(deps): bump trim-off-newl… (compare)
dependabot[bot] on npm_and_yarn
chore(deps): bump trim-off-newl… (compare)
jayphelps on master
docs(examples): update to use `… (compare)
Hey guys, I think to my self if there is a better way to take one epic that can handle with multi-action.
for example, I have an epic that is in charge of my module (is some page in my app). I use merge
operator to do that, but I'm not sure if it can cause a performance problem.
I'm not sure I'm clear so I post an example below.
const merchEpic: Epic = (actions$, state$: StateObservable, { api }: EpicDependencies) => {
const getNavigation$ = actions$.pipe(
ofType(GET_NAVIGATION),
switchMap...
)
const productFetched$ = actions$.pipe(
ofType(PRODUCT_FETCHED),
switchMap...
)
const deleteProduct$ = actions$.pipe(
ofType(DELETE_PRODUCT),
switchMap...
)
const editProduct$ = actions$.pipe(
ofType(EDIT_PRODUCT),
switchMap...
)
const addProduct$ = actions$.pipe(
ofType(ADD_PRODUCT),
switchMap...
)
const reorderProduct$ = actions$.pipe(
ofType(REORDER_PRODUCT),
switchMap...
)
and more...
return getNavigation$.pipe(
merge(productFetched$),
merge(deleteProduct$),
merge(editProduct$),
merge(addProduct$),
merge(reorderProduct$),
merge(updateUniverseName$)
)
}
export { merchEpic }
see my booksepic it is almost same as characters and showing books
https://the-one-api.herokuapp.com/v1/character i am trying with Bearer Token
am stuck can you please help what is issue
@Sawtaytoes , @Kryotasin , @ktajpuri and @evertbouw
try
-catch
.
import { mergeMap, map } from "rxjs/operators";
import { ofType } from "redux-observable";
import { ajax } from "rxjs/ajax";
import { Observable } from "rxjs";
import {
FETCH_BOOKS,
fetchBooksFailure,
fetchBooksSuccess,
} from "../actions/index";
let api = "https://the-one-api.herokuapp.com/v1/book";
function fetchBooksEpic(action$) {
try {
return action$.pipe(
ofType(FETCH_BOOKS),
mergeMap(() => {
return ajax.get(api).pipe(
map((data) => Array.from(data.docs))
);
}),
map((books) => fetchBooksSuccess(books))
);
} catch (error) {
Observable.of(fetchBooksFailure(error.message));
}
}
export default fetchBooksEpic;
import { API_TOKEN, api } from "../constants/demoConstants";
import { switchMap, mergeMap, map, flatMap } from "rxjs/operators";
import { ofType } from "redux-observable";
import { ajax } from "rxjs/ajax";
import { Observable } from "rxjs";
import {
FETCH_CHARACTERS,
fetchCharactersSuccess,
fetchCharactersFailure,
} from "../actions/index";
let xhr = new XMLHttpRequest();
xhr.open("GET", api, true);
xhr.setRequestHeader("Authorization", "Bearer " + API_TOKEN);
xhr.send();
function fetchCharactersEpic(action$) {
try {
return action$.pipe(
ofType(FETCH_CHARACTERS),
mergeMap(() => {
return ajax.getJSON(api).pipe(
flatMap((data) => Array.from(data.docs.text())),
map((characters) =>
characters.map((character) => ({
id: character._id,
name: character.name,
wikiUrl: character.wikiUrl,
spouse: character.spouse,
}))
)
);
}),
map((characters) => fetchCharactersSuccess(characters))
);
} catch (error) {
Observable.of(fetchCharactersFailure(error.message));
}
}
export default fetchCharactersEpic;
import { combineEpics } from "redux-observable";
import fetchWhiskiesEpic from "./fetchWhiskiesEpic";
import fetchBooksEpic from "./fetchBooksEpic";
import fetchCharactersEpic from "./fetchCharactersEpic";
export default combineEpics(
fetchBooksEpic,
fetchWhiskiesEpic,
fetchCharactersEpic
);
import {
FETCH_CHARACTERS,
FETCH_CHARACTERS_FAILURE,
FETCH_CHARACTERS_SUCCESS,
} from "../actions/index";
const initialState = {
isLoaded: false,
characters: [],
error: null,
};
const charactersReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_CHARACTERS:
return {
...state,
// whenever we want to fetch the CHARACTERS, set isLoading to true to show a spinner
isLoading: true,
error: null,
};
case FETCH_CHARACTERS_SUCCESS:
return {
characters: [...action.payload],
// whenever the fetching finishes, we stop showing the spinner and then show the data
isLoading: false,
error: null,
};
case FETCH_CHARACTERS_FAILURE:
return {
characters: [],
isLoading: false,
// same as FETCH_CHARACTERS_SUCCESS, but instead of data we will show an error message
error: action.payload,
};
default:
return state;
}
};
export default charactersReducer;
import { combineReducers } from "redux";
import { connectRouter } from "connected-react-router";
import whiskysReducer from "./whiskysReducer";
import booksReducer from "./booksReducer";
import charactersReducer from "./charactersReducer";
const rootReducer = (history) =>
combineReducers({
router: connectRouter(history),
whiskies: whiskysReducer,
books: booksReducer,
characters: charactersReducer,
});
export default rootReducer;
import {
FETCH_BOOKS,
FETCH_BOOKS_FAILURE,
FETCH_BOOKS_SUCCESS,
} from "../actions/index";
const initialState = {
isLoaded: false,
books: [],
error: null,
};
const booksReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_BOOKS:
return {
...state,
// whenever we want to fetch the BOOKS, set isLoading to true to show a spinner
isLoading: true,
error: null,
};
case FETCH_BOOKS_SUCCESS:
return {
books: [...action.payload],
// whenever the fetching finishes, we stop showing the spinner and then show the data
isLoading: false,
error: null,
};
case FETCH_BOOKS_FAILURE:
return {
books: [],
isLoading: false,
// same as FETCH_BOOKS_SUCCESS, but instead of data we will show an error message
error: action.payload,
};
default:
return state;
}
};
export default booksReducer;
function fetchCharactersEpic(action$) {
return action$.pipe(
ofType(FETCH_CHARACTERS),
mergeMap(() =>
ajax.getJSON(api, { Authorization: `Bearer ${API_TOKEN}` }).pipe(
flatMap((data) => Array.from(data.docs.text())),
map((characters) =>
characters.map((character) => ({
id: character._id,
name: character.name,
wikiUrl: character.wikiUrl,
spouse: character.spouse,
}))
),
map((characters) => fetchCharactersSuccess(characters)),
catchError((error) =>
Observable.of(fetchCharactersFailure(error.message))
)
)
)
);
}
That's a good question. This is actually a React problem.
Server-side React doesn't run useEffect
and will not work async.
The only way you can get this to work as you expect is to subscribe to store
and wait for the values you want to exist. After that, render React server-side.
I know it's weird, but React doesn't have a way to run and run again when certain changes are made server-side. The way it works, it renders HTML and that's it. So you have to do all the work before React runs, then feed React all the data necessary to get the correct server-side output you're wanting.
Actions must be plain objects. Use custom middleware for async actions.
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 => {
console.log('vals');
console.log(val);
return Creators.fetchMenuSuccess(val);
})
)
}
)),
);
};
Hi everyone, I have a question about combineLatest in Epic, after my app has been initiated, it will dispatch initAppCompleted only once, after that every time I jump to the focus page, it will dispatch fetchFocusPage action and request API.
Based on the info in https://stackoverflow.com/questions/42426908/how-to-delay-one-epic-until-another-has-emitted-a-value, using combineLatest must be alright.
But what happened is: after clicking the focus page, only the first time it outputs 'focus page epic entering'. Only If I manually dispatch initAppCompleted action after clicking the page, otherwise there is no request happening. I really don't know why.
here is my code(using Rxjs 6.5.4 & redux-observable 1.2.0):
export const focusPageFetchEpic: Epic<AnyAction, AnyAction, any> = (
action$: ActionsObservable<Action>,
state$: StateObservable<any>
) =>
combineLatest([
action$.pipe(filter(isActionOf(initAppCompleted)), tap(() => console.log('init action comming')), take(1)),
action$.pipe(filter(isActionOf(fetchFocusPage)), tap(() => console.log('fetch action comming')))
]).pipe(
tap(() => console.log('focus page epic entering'))
)
@fisherspy It shouldn't matter about the payload. If both observables fire once, then it outputs values in your pipe
. That's how combineLatest
works. It waits for observables to output something once. After that happens, then you start receiving values in the combineLatest().pipe
.
If you want something to emit when either observable emits, use merge
instead of combineLatest
.
import { ActionsObservable } from 'redux-observable'
// ...
export const mockAjaxResponse = (
mockedResponse,
) => ({
ajax: () => (
ActionsObservable
.of({
response: mockedResponse,
})
),
})
// ...
fetchUserEpic(
// action$
ActionsObservable
.of(
fetchUser({ // this is a Redux action creator
username: 'test@test.com',
password: 'test',
})
),
// state$
new BehaviorSubject({
user: null,
}),
// Dependency injection
mockAjaxResponse(),
)
@xavier7179 I'd need a bit of code to work with. Can't visualize what you mean.
You can always do something like dispatching an action from those epics making API calls and then have a single epic do something like:
combineLatest(
action$.pipe(ofType('FIRST_API_RESPONSE')),
action$.pipe(ofType('SECOND_API_RESPONSE')),
)
@Sawtaytoes sure!
The schema I'm depicting in my mind is something like (avoiding parallelism and keeping the order):
So far, based on your previous suggestion, I did something like this (which it is not fully including all scenarios but epic error):
export function fetchEpics(action$) {
return action$.pipe(
ofType(FETCH_EPIC1),
take(1),
mergeMap(
(original_action) =>
concat(
of(fetchEPIC1(original_action.payload)),
action$.ofType(FETCH_EPIC2).pipe(
take(1),
map(() => fetchEPIC2(original_action.payload)
)
),
),
)
);
}
But as soon as I try a third one, I got stuck.