Skip to content

Async operations and side effects

Timo Dörr edited this page Aug 22, 2018 · 6 revisions

Motivation

We use reducers to modify our state and reducers must be pure functions which are free of side-effects. Webapplications are however subject to quite some side effects (http requests, user inputs, etc.). We therefore need a way to handle asynchronous operations and side effects in a clean way.

In Redux-land, several approaches exist to handle async operations (i.e. ActionCreators) that often require custom libraries/middlewares to be handled properly (examples of such libraries are redux-thunk or redux-saga). For Angular applications, the ngrx effects library introduces the concept of Effects to handle asynchronous operations.

Side effects in reactive-state

Since reactive-state builds on top RxJS - which is a library that makes it easy to combine and chain asynchronous operations - handling side effects is really easy with reactive-state, and no special middlewares or additional libraries are required.

We just use the multitude of available operators in RxJS to map our Actions (which are Observables/Subjects) to perform any asynchronous operations, like HTTP requests. The resulting observable can be used as an Action and directly be used in the .addReducer() function of a Store:

interface AppState {
    myIpAddress: string;
}

const store: Store<AppState> = Store.create({ myIpAddress: "" });

const getIpAddress = new Subject<void>();

const getIpAddressEffect = getIpAddress.pipe(
    switchMap(() => fetch("http://ip.jsontest.com")),
    switchMap(response => response.json()),
    map(dto => dto.ip)
);

const ipAddressReducer: Reducer<AppState, string> =
    (state, ip) => ({ ...state, myIpAddress: ip });

store.addReducer(getIpAddressEffect, ipAddressReducer);

store.watch().pipe(
    filter(state => state.myIpAddress.length > 0)
).subscribe(state => console.log("My IP address is: ", state.myIpAddress));

// dispatch the action, only this will trigger the HTTP request!
getIpAddress.next();

// output: "My IP address is: 1.2.3.4"

In the above example, we passed an Observable to the .addReducer() function. This is perfectly fine and encouraged in reactive-state. The Observable that we wired, is dependent on and action that we can trigger whenever we want, especially in an async way. When adding an Observable via .addReducer(), you can manually specify a string constant to name this "pseudo-action". Naming is optional, but useful for logging and debugging, especially when using the dev tools:

store.addReducer(getIpAddressEffect, ipAddressReducer, "IP_ADDRESS_RECEIVED");