Base action with common logic
In Async Redux, all actions must extend ReduxAction<State>
.
For example:
import { ReduxAction } from 'async-redux-react';
import { State } from 'State';
class Increment extends ReduxAction<State> {
reduce() {
return (state: State) => state.increment();
}
}
In all the code I show in this documentation, you'll see I usually write extend Action
instead of extend ReduxAction<State>
.
This is because I'm assuming you have previously defined your own abstract base action class
called simply Action
, that itself extends ReduxAction<State>
. Then, you may have all your
actions extend this Action
class instead.
This is how you would define the Action
class:
import { ReduxAction } from 'async-redux-react';
import { State } from 'State';
export abstract class Action extends ReduxAction<State> { }
Remember this is optional, but recommended. The reason to do this is twofold:
-
First, you'll avoid writing
extends ReduxAction<State>
in every action class. Now, you'll need to writeextends Action
instead, which is simpler. -
And second, to have a common place to put any common logic that all your actions should have access to. More on that later.
Common logic
Suppose we have the following app state:
class State {
constructor(
public items: Item[] = [],
public selectedItem: Item | null = null
) {}
}
class Item {
constructor(public id: string) {}
}
And then we have an action, which selects an item by id
:
class SelectItem extends Action {
constructor(public id: number) { super(); }
reduce() {
let item = state.items.find(item => item.id === this.id);
if (item === undefined) throw new UserException('Item not found');
return state.copy({selectedItem: item});
}
}
You would use it like this:
var item = new Item('A');
dispatch(new SelectItem(item));
Now, suppose we have a lot of actions that need to access the items
and selectedItem
properties.
We could add getters and selectors to the base Action
class:
abstract class Action extends ReduxAction<State> {
// Getters shortcuts
get items(): Item[] { return this.state.items; }
get selectedItem(): Item { return this.state.selectedItem; }
// Selectors
findById(id: number): Item | undefined { return this.items.find(item => item.id === id); }
searchByText(text: string): Item | undefined { return this.items.find(item => item.text.includes(text)); }
get selectedIndex(): number { return this.selectedItemId !== null ? this.items.findIndex(item => item.id === this.selectedItemId) : -1; }
}
And then your actions have an easier time accessing the store state:
class SelectItem extends Action {
constructor(public id: number) { super(); }
reduce() {
let item = this.findbyId(this.id); // Here!
if (item === undefined) throw new UserException('Item not found');
return state.copy({selectedItem: item});
}
}
The difference above is that, instead of writing:
let item = state.items.find(item => item.id === this.id);
You can simply write:
let item = this.findbyId(this.id);
It may seem a small reduction of boilerplate, but it adds up.
In practice, your base action class may end up containing a lot of elaborate "selector functions", which then can be used by all your actions.
The only requirement is that your actions now
extend Action
instead of ReduxAction<State>
.