Multiple actions with dependencies in Redux-Observable - javascript

I am using Redux-Observable Epic in a React & Redux project. I have multiple actions need to emit, originally this is what I have been doing,
1) Catch the START_ACTION in the Epic
2) Fetch remote data
3) Return a new Observanble
for example:
import fetch from 'isomorphic-fetch';
import {Observable} from 'rxjs/Observable';
import {switchMap} from 'rxjs/operator/switchMap';
import {mergeMap} from 'rxjs/operator/mergeMap';
const fetchAsync = (arg) => {
// return an observable of promise
return Observable::from(fetch(url + arg));
}
export function myEpic = (action$) => {
action$.ofType(START_ACTION)
::switchMap(action => {
return fetchAsync('/action.payload')
::mergeMap(result => {
return Observable::of({type: ACTION_COMPLETE, payload: result})
})
})
};
Now what if I have another action SECOND_ACTION need to be emitted after the START_ACTION and before the ACTION_COMPLETE ? In other word, without making sure the SECOND_ACTION hits the reducer, the ACTION_COMPLETE should not be emitted.
I could write another separate Epic function to do this, but is there any other easier way?

To simplify the question, I just want to emit the SECOND_ACTION before the async.
To emit another action before performing the fetchAsync, you can either use Observable.concat or the startWith operator, which is basically sugar for the concat.
export function myEpic = (action$) => {
action$.ofType(START_ACTION)
::switchMap(action => {
return fetchAsync('/action.payload')
::map(result => ({ type: ACTION_COMPLETE, payload: result })
::startWith({ type: SECOND_ACTION })
})
};
Since SECOND_ACTION synchronously follows START_ACTION, keep in mind that often you should just have your reducers listen for START_ACTION instead of emitting another action. Multiple reducers transitions state from the same action is normal and one of the primary benefits of redux.
That said, there are certainly some times where the separation of concerns is more ideal, so this is more of a general tip.
Previous answer
If you want to emit two actions sequentially you can pass the additional actions as arguments to Observable.of(...actions) since it accepts any number of arguments and emits them sequentially.
export function myEpic = (action$) => {
action$.ofType(START_ACTION)
::switchMap(action => {
return fetchAsync('/action.payload')
::mergeMap(result => {
return Observable::of(
{ type: SECOND_ACTION },
{ type: ACTION_COMPLETE, payload: result }
)
})
})
};
If this isn't what you meant, I apologize. The question isn't clear.

Related

Actions must be plain objects, even though it seems my actions are already plain

I am developing an app with React Native and Redux. I have some authentication and localisation logic handled by Redux, and now I want to fetch needed data from remote API using Redux as well.
An example of what I had for authentication:
actions.auth.js
export function saveAuthToken(authToken) {
return {
type: "SAVE_AUTH_TOKEN",
authToken
};
}
...
reducers.auth.js
export function authToken(state = "", action) {
switch (action.type) {
case "SAVE_AUTH_TOKEN":
return action.authToken;
...
default:
return state;
}
}
What I am now trying to add:
actions.data.js
export function fetchData() {
return {
type: "FETCH_DATA"
};
}
...
reducers.data.js
export function dataList(state = {}, action) {
switch (action.type) {
case "FETCH_DATA":
return {};
...
default:
return state
}
}
For test purposes, I now don't even make any calls to API. Just try to return {} for data in any case.
Even though I think that I made everything identical to the way I handled authentication (which worked), when I try to call:
store.dispatch('FETCH_DATA');
I get the following error:
Error: Actions must be plain objects. Use custom middleware for async actions.
I don't quite get where exactly I try to use async actions and why my actions aren't plain objects.
Any help is appreciated.
store.dispatch() takes an action as an argument.
And, as the error says, the action must be plain object.
The error is obvious, because
what you are passing to dispatch() is a string and not an object.
Here is what you would want to do:
import { fetchData } from '<file-path-here>'
store.dispatch(fetchData())
You see, the fetchData() would then return a plain object which is required by store.dispatch()
If you are not using any custom middleware then your action must be plain object in your case like below
store.dispatch({
type : 'FETCH_DATA',
payload : {} // if any pass here
})
You can not dispatch action as string it must have a plain object like
{
type: 'YOUR_ACTION',
payload: yourPayload
}
if you want to pass function or else you must use custom middleware it will also use to dispatch function you can use thunk as middleware or redux-saga.
This link will help you.
redux-thunk
redux-saga
It will help you.
reducers.data.js
export function dataList(state = {}, action) {
switch (action.type) {
case "FETCH_DATA":
return {...state};
...
default:
return state
}
}
export function fetchData() { return dispatch{ type: "FETCH_DATA" }; }
use dispatch to dispatch your actions

How to unit test form validation in Aurelia

I am trying to implement some unit tests on a form to see if the validation rules are working as expected.
from this page : https://github.com/aurelia/testing/issues/63
I found this implementation : https://github.com/aurelia/validation/blob/master/test/validate-binding-behavior.ts
and I tried to implement it in my project
login.spec.js
import {bootstrap} from 'aurelia-bootstrapper';
import {StageComponent} from 'aurelia-testing';
import {PLATFORM} from 'aurelia-pal';
import { configure, blur, change } from './shared';
import { Login } from './login';
describe('ValidateBindingBehavior', () => {
it('sets validateTrigger', (done) => {
const component = StageComponent
.withResources(PLATFORM.moduleName('features/account/login/login'))
.inView('<login></login>')
.boundTo({});
component.bootstrap(configure);
let viewModel;
const renderer = { render: jasmine.createSpy() };
component.create(bootstrap)
// grab some references.
.then(() => {
viewModel = component.viewModel;
viewModel.controller.addRenderer(renderer);
})
.then(() => expect(viewModel.controller.errors.length).toBe(0))
.then(() => blur(viewModel.firstName))
.then(() => expect(viewModel.controller.errors.length).toBe(1))
.then(() => component.dispose())
.then(done);
});
});
login.js
import { inject, NewInstance } from 'aurelia-dependency-injection';
import { ValidationController } from 'aurelia-validation';
import { User } from './login.model';
#inject(NewInstance.of(ValidationController), User)
export class Login {
constructor(controller, user) {
this.controller = controller;
this.firstName = '';
this.lastName = '';
this.userName = '';
this.showForm = true;
this.user = user;
}
};
login.model.js
import {ValidationRules} from 'aurelia-validation';
export class User {
firstName = '';
lastName = '';
userName = '';
constructor() {
ValidationRules
.ensure('firstName')
.required()
.ensure('lastName')
.required()
.minLength(10)
.ensure('userName')
.required()
.on(this);
}
}
shared.js
import {DOM, PLATFORM} from 'aurelia-pal';
export function configure(aurelia) {
return aurelia.use
.standardConfiguration()
.plugin(PLATFORM.moduleName('aurelia-validation'))
}
export function blur(element) {
element.dispatchEvent(DOM.createCustomEvent('blur', {}));
return new Promise(resolve => setTimeout(resolve));
}
export function change(element, value) {
element.value = value;
element.dispatchEvent(DOM.createCustomEvent('change', { bubbles: true }));
return new Promise(resolve => setTimeout(resolve));
}
and here is a piece of html markup :
<div>
<input ref="firstName" type="text" value.bind="user.firstName & validateOnBlur"
validation-errors.bind="firstNameErrors">
<label style="display: block;color:red" repeat.for="errorInfo of firstNameErrors">
${errorInfo.error.message}
</label>
</div>
<div>
in the spec, when I blur the element I expect to get one error, but "controller.errors" is always an empty array. and I get this for the failed message :
Error: Expected 0 to be 1.
UPDATE 1:
I tried to validate manually, so I added this in my spec :
.then(()=>
viewModel.controller.validate({object: viewModel.user, propertyName: 'firstName' })
)
and it works fine, but the blur and change functions don't trigger validation.
UPDATE 2:
I changed it like "Sayan Pal" suggested. and it works now but with a tiny problem. when I "blur" the element once it shows one error. but when I "blur" several elements ( let's say three ) it doesn't show the last error. in this case controller.errors.length would be 2.
I can blur the last element two times to get the correct length of errors. but I think there should be a better solution.
.then(() => blur(viewModel.firstName))
.then(() => blur(viewModel.userName))
.then(() => blur(viewModel.lastName))
.then(() => blur(viewModel.lastName))
I think instead of using createCustomEvent you simply need to do element.dispatchEvent(new Event("blur"));. Same goes for change event.
This has always worked for me, and hope it will help you too :)
On related note, I use a default ValidationController generator factory method that ensures the default trigger as follows.
import { validateTrigger, ValidationControllerFactory } from "aurelia-validation";
...
const validationController = validationControllerFactory.createForCurrentScope();
validationController.changeTrigger(validateTrigger.changeOrBlur);
Update after OP updated the question
It is difficult to say why it is happening, without debugging. As I don't see any imminent problem in your test code, my assumption is that it is a timing issue. The main idea is that you need to wait for the change to happen. There are several ways you can do it, all of those needs change in how you are asserting.
One way to do it is to employ a promise with a timeout that polls in a regular interval for the change. And then wait for the promise.
Or you can use TaskQueue to queue your assertion, and after the assertion call done. This looks something like below.
new TaskQueue().queueMicroTask(() => {
expect(foo).toBe(bar);
done();
});
Other alternative is to use cypress as an e2e test framework. Out of the box, Cypress waits for the change to happen until times out.
Choose what best fits your need.

Angular - detecting when two subscriptions have updated

In a component, in ngOnInit() I've got two subscriptions to a data service. I want to do some processing once both subscriptions have returned. Whats the best way to do this? I can just process at the end of each, this just seems a little inefficient and won't work for which ever subscription activates first,
Thanks,
Component.TS
ngOnInit()
{
this.dataService.dataA().subscribe((dataAJSON) =>
{
this.dataA= dataAJSON
}
this.dataService.dataB().subscribe((dataBJSON) =>
{
this.dataB= dataBJSON
}
DataService
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class PMDataService
{
constructor(public http : Http)
{
}
dataA()
{
var dataA: any;
var json;
dataA= this.http.get("./assets/dataa.json")
.map(res => res.json());
return dataA
}
dataB()
{
var dataB: any;
var json;
dataB= this.http.get("./assets/datab.json")
.map(res => res.json());
return dataB
}
}
You can use Observable#forkJoin function on the Observables. It emits the last value from each when all observables complete,
Observable.forkJoin(this.dataService.dataA(), this.dataService.dataB())
.subscribe(val => /* val is an array */)
The method used depends on how you want to receive the data:
You can use the zip function. Emits once when all have emitted once. So similar to Promise.all except not on completion.
Observable.zip(obs1, obs2).subscribe((val) => { ... });
You can use the forkJoin function. Emits once when all have completed. So exactly like Promise.all.
Observable.forkJoin(obs1, obs2).subscribe((val) => { ... });
You can use the merge function. Emits in order of emission so could be 1st then 2nd or 2nd then 1st:
obs1.merge(obs2).subscribe((val) => { ... });
You can use concat function. Emits in order 1st then 2nd regardless if 2nd emits first:
obs1.concat(obs2).subscribe((val) => { ... });
It's best practice to split these up into a couple lines for clarity.
const obs1 = Rx.Observable.of(1,2,3);
const obs2 = Rx.Observable.of(1,2,3);
const example = Observable.zip(obs1, obs2);
//const example = Observable.forkJoin(obs1, obs2);
//const example = obs1.merge(obs2);
//const example = obs1.concat(obs2);
example.subscribe(val => { ... });
You could use the operator Zip or CombineLatest from rxjs.
See ReactiveX operators
You could do something like this:
Observable.zip(
this.http.get("./assets/dataa.json"),
this.http.get("./assets/dataa.json")
.take(1)
.map(values => [values[0].json(), values[1].json()])
.subscribe(values => {
// do something with my values
});
You could You can use concat to combine the observables and return a single observable.
Subscribe to observables in order as previous completes, emit values
changed service code
import 'rxjs/add/operator/concat';
export class PMDataService
{
data(){
return this.dataA().concat(this.dataB());
}
// methods dataA and dataB are unchanged, some of the constructor
}
Component code
ngOnInit(){
this.dataService.data().subscribe((dataJSON) =>
{
this.dataA= dataAJSON[0];
this.dataB= dataAJSON[1];
}
}

Return an empty Observable

The function more() is supposed to return an Observable from a get request
export class Collection {
public more = (): Observable<Response> => {
if (this.hasMore()) {
return this.fetch();
} else {
// return empty observable
}
};
private fetch = (): Observable<Response> => {
return this.http.get("some-url").map((res) => {
return res.json();
});
};
}
In this case I can only do a request if hasMore() is true, else I get an error on subscribe() function subscribe is not defined, how can I return an empty Observable?
this.collection.more().subscribe(
(res) => {
console.log(res);
}, (err) => {
console.log(err);
}
);
With the new syntax of RxJS 5.5+, this becomes as the following:
// RxJS 6
import { EMPTY, empty, of } from "rxjs";
// rxjs 5.5+ (<6)
import { empty } from "rxjs/observable/empty";
import { of } from "rxjs/observable/of";
empty(); // deprecated use EMPTY
EMPTY;
of({});
Just one thing to keep in mind, EMPTY completes the observable, so it won't trigger next in your stream, but only completes. So if you have, for instance, tap, they might not get trigger as you wish (see an example below).
Whereas of({}) creates an Observable and emits next with a value of {} and then it completes the Observable.
E.g.:
EMPTY.pipe(
tap(() => console.warn("i will not reach here, as i am complete"))
).subscribe();
of({}).pipe(
tap(() => console.warn("i will reach here and complete"))
).subscribe();
For typescript you can specify generic param of your empty observable like this:
import 'rxjs/add/observable/empty'
Observable.empty<Response>();
RxJS6 (without compatibility package installed)
There's now an EMPTY constant and an empty function.
import { Observable, empty, EMPTY, of } from 'rxjs';
//This is now deprecated
var delay = empty().pipe(delay(1000));
var delay2 = EMPTY.pipe(delay(1000));
Observable.empty() doesn't exist anymore.
Several ways to create an Empty Observable:
They just differ on how you are going to use it further (what events it will emit after: next, complete or do nothing) e.g.:
Observable.never() - emits no events and never ends.
Observable.empty() - emits only complete.
Observable.of({}) - emits both next and complete (Empty object literal passed as an example).
Use it on your exact needs)
In my case with Angular2 and rxjs, it worked with:
import {EmptyObservable} from 'rxjs/observable/EmptyObservable';
...
return new EmptyObservable();
...
Yes, there is am Empty operator
Rx.Observable.empty();
For typescript, you can use from:
Rx.Observable<Response>.from([])
Since all the answers are outdated, I will post the up to date answer here
In RXJS >= 6
import { EMPTY } from 'rxjs'
return EMPTY;
You can return Observable.of(empty_variable), for example
Observable.of('');
// or
Observable.of({});
// etc
Differents way to return empty observable :
Observable.from({});
Observable.of({});
EMPTY
https://www.learnrxjs.io/learn-rxjs/operators/creation/empty
Or you can try ignoreElements() as well
RxJS 6
you can use also from function like below:
return from<string>([""]);
after import:
import {from} from 'rxjs';
Came here with a similar question, the above didn't work for me in: "rxjs": "^6.0.0", in order to generate an observable that emits no data I needed to do:
import {Observable,empty} from 'rxjs';
class ActivatedRouteStub {
params: Observable<any> = empty();
}
Try this
export class Collection{
public more (): Observable<Response> {
if (this.hasMore()) {
return this.fetch();
}
else{
return this.returnEmpty();
}
}
public returnEmpty(): any {
let subscription = source.subscribe(
function (x) {
console.log('Next: %s', x);
},
function (err) {
console.log('Error: %s', err);
},
function () {
console.log('Completed');
});
}
}
let source = Observable.empty();
You can return the empty observable with all different ways but challenge is to to return it with the expected type -
Here is the way to create a empty observable with type -
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(this.setHeaders(req))
.pipe(
catchError((error: HttpErrorResponse) => {
// you write your logic and return empty response if required
return new Observable<HttpEvent<any>>();
}));
}
there is another: EMPTY const
Replaced with the EMPTY constant or scheduled (e.g. scheduled([], scheduler)). Will be removed in v8. (got this form phpstorm hint)

Redux: using thunk middleware and combineReducers introduces extra key to getState

Problem: When using thunk middleware before introducing Redux.combineReducers, the getState passed to the thunk correctly returns an object with the correct keys. After refactoring to use Redux.combineReducers, the getState passed to the thunk now returns an object with nested keys. See code below which (hopefully) illustrates my point. This could lead to a potential maintenance nightmare of having to constantly grab the correct key for any thunk method that accesses state.
Question: Is there a simple way to set the correct context key within the thunk? The code feels brittle when I combine reducers and have to insert keys to access the correct state. Am I missing something simple?
Before code:
const Redux = require('redux'),
Thunk = require('redux-thunk');
// this is an action generator that returns a function and is handled by thunk
const doSomethingWithFoo = function() {
return function(dispatch, getState) {
// here we're trying to get state.fooValue
const fooValue = getState().fooValue;
dispatch({ type: "DO_SOMETHING", fooValue });
}
};
// this is a simple action generator that returns a plain action object
const doSimpleAction = function(value) {
// we simply pass the value to the action.
// we don't have to worry about the state's context at all.
// combineReducers() handles setting the context for us.
return { type: "SIMPLE_ACTION", value };
}
const fooReducer(state, action) {
// this code doesn't really matter
...
}
const applyMiddleware = Redux.applyMiddleware(Thunk)(Redux.createStore);
const fooStore = applyMiddleware(fooReducer);
After code (introducing a more global appStore):
// need to rewrite my thunk now because getState returns different state shape
const doSomethingWithFoo = function() {
return function(dispatch, getState) {
// here we're trying to get state.fooValue, but the shape is different
const fooValue = getState().foo.fooValue;
dispatch({ type: "DO_SOMETHING", fooValue });
}
};
const appReducers = Redux.combineReducers({
foo: fooReducer,
bar: barReducer,
});
const appStore = applyMiddleware(appReducers);
After thinking about it some more, I think the answer is to refactor the doSomethingWithFoo action generator so that it accepts fooValue as a parameter. Then I don't have to worry about state object shape changing.
const doSomethingWithFoo(fooValue) {
return function(dispatch, getState) {
// now we don't have to worry about the shape of getState()'s result
dispatch({ type: "DO_SOMETHING", fooValue });
}
}
You're over-thinking things. By definition, store.getState() returns the entire state, and combineReducers() pulls together multiple sub-reducers into a larger object. Both are working as intended. You're writing your own application, so you're responsible for how you want to actually organize your state shape and deal with it. If you feel things are too "brittle" this way, it's up to you to find a good way to structure things, but that's not a problem with Redux.
Also, using getState() in an action creator to determine what to do IS an entirely valid approach. In fact, the Reducing Boilerplate section of the Redux docs even does that as a demonstration:
export function addTodo(text) {
// This form is allowed by Redux Thunk middleware
// described below in “Async Action Creators” section.
return function (dispatch, getState) {
if (getState().todos.length === 3) {
// Exit early
return
}
dispatch(addTodoWithoutCheck(text))
}
}

Categories