API Reference
Ship<Effect, Commit, State, A>Snapshot<Effect, Commit>allcallcommitgetStatemapmiddlewarerunsimulatesnap
Ship<Effect, Commit, State, A>
The type of a ship. A ship is a generator and can be defined using the function* syntax.
Effectthe type of the side effects the ship can call. We often use a singleEffecttype for a whole programCommitthe type of the commits the ship can commitStatethe type of the state as visible from the shipAthe type of the value returned by the ship (oftenvoid)
Example
A controller to load a random gif:
export function* control(action: Action): Ship.Ship
switch (action.type) {
case 'Load': {
yield* Ship.commit({
type: 'LoadStart',
});
const result = yield* Effect.httpRequest(
`http://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=${action.tag}`
);
const gifUrl: string = JSON.parse(result).data.image_url;
yield* Ship.commit({
type: 'LoadSuccess',
gifUrl,
});
return;
}
default:
return;
}
}
Snapshot<Effect, Commit>
The type of the snapshot of an execution of a ship. A snapshot includes the side effects ran by a ship, as well as their execution order (sequential or concurrent). You can take a snapshot with the redux-ship-logger dev tools or with the snap function.
Effectthe type of effects in the snapshotCommitthe type of commits in the snapshot
Example
The snapshot of a controller loading a random gif:
[
{
"type": "Commit",
"commit": {
"type": "LoadStart"
}
},
{
"type": "Effect",
"effect": {
"type": "HttpRequest",
"url": "http://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=minion"
},
"result": [...]
},
{
"type": "Commit",
"commit": {
"type": "LoadSuccess",
"gifUrl": "http://media3.giphy.com/media/HyanD1KpfzPiw/giphy.gif"
}
}
]
all
all<Effect, Commit, State, A>(
ships: Ship<Effect, Commit, State, A>[]
): Ship<Effect, Commit, State, A[]>
Returns the array of results of the ships by running them in parallel. This is the equivalent of Promise.all in Ship.
shipsan array of ships to execute concurrently
If you have a fixed number of tasks with different types of result to run in parallel, use:
all2(ship1, ship2)
all3(ship1, ship2, ship3)
...
all7(ship1, ship2, ship3, ship4, ship5, ship6, ship7)
The result type is a tuple with the same types as the arguments:
all3<Effect, Commit, State, A1, A2, A3>(
ship1: Ship<Effect, Commit, State, A1>,
ship2: Ship<Effect, Commit, State, A2>,
ship3: Ship<Effect, Commit, State, A3>
): Ship<Effect, Commit, State, [A1, A2, A3]>
Example
To concurrently get three random gifs:
const gifUrls = yield* Ship.all(['cat', 'minion', 'dog'].map(function* (tag) {
const result = yield* Effect.httpRequest(
`http://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=${tag}`
);
return JSON.parse(result).data.image_url;
}));
call
call<Effect, Commit, State>(
effect: Effect
): Ship<Effect, Commit, State, any>
Calls the serialized effect effect. The type of the result is any because it depends on the value of the effect, which is only known at runtime.
effectthe effect to call
Example
To prevent type errors, we recommend to wrap your calls to call with one wrapper per kind of effect. For example, if you have an effect HttpRequest which always returns a string:
export function httpRequest<Commit, State>(url: string): Ship<Effect, Commit, State, string> {
return Ship.call({
type: 'HttpRequest',
url,
});
}
commit
commit<Effect, Commit, State>(
commit: Commit
): Ship<Effect, Commit, State, void>
Commits a commit of type Commit and waits for its termination.
committhe commit to apply
Example
To commit the result of a successful HTTP request:
yield* Ship.commit({
type: 'LoadSuccess',
gifUrl,
});
getState
getState<Effect, Commit, State, A>(
selector: (state: State) => A
): Ship<Effect, Commit, State, A>
Returns a part of the current state by applying a selector.
selectora selector to extract the useful part of the current state
Example
To get the current gif in the store:
const currentGif = yield* Ship.getState(state => state.randomGif.gifUrl);
map
map<Effect, SubCommit, SubState, Commit, State, A>(
liftCommit: (subCommit: SubCommit) => Commit,
extractState: (state: State) => SubState,
ship: Ship<Effect, SubCommit, SubState, A>
): Ship<Effect, Commit, State, A>
A function useful to compose nested components. Lifts a ship with access to "small set" of commits SubCommit and a "small set" of states SubState to a ship with access to the "larger sets" Commit and State.
liftCommitlifts a sub-commit to a commitextractStateextract a sub-state from a stateshipthe ship to map
Example
To embed a controller retrieving one random gif into a controller retrieving two random gifs:
return yield* Ship.map(
commit => ({type: 'First', commit}),
state => state.first,
RandomGifController.control(action.action)
);
middleware
middleware<Action, Effect, Commit, State>(
runEffect: (effect: Effect) => any | Promise<any>,
control: (action: Action) => Ship<Effect, Commit, State, void>
): ReduxMiddleware
Returns a Redux middleware to connect Redux Ship to Redux.
runEffectthe function to evaluate a serialized side effect (may return a promise for asynchronous effects)controlthe function to evaluate anactionwith a ship
The Ship middleware eats all actions: it does not let actions propagate to the next middleware. If you want an action to propagate use Ship.commit explicitly.
When a Ship.commit occurs, the commit is dispatched to the next middleware rather than to the whole middleware chain. This is so to prevent loops of commits. Thus, this is not possible to call another ship with Ship.commit. Instead, use the yield* keyword to directly call the corresponding ship.
Example
import {applyMiddleware, createStore} from 'redux';
const middlewares = [
Ship.middleware(runEffect, control),
otherMiddleware
];
const store = createStore(reduce, initialState, applyMiddleware(...middlewares));
run
run<Effect, Commit, State, A>(
runEffect: (effect: Effect) => any | Promise<any>,
store: {
dispatch: (commit: Commit) => void | Promise<void>,
getState: () => State
},
ship: Ship<Effect, Commit, State, A>
): Promise<A>
Runs a ship and its side effects by evaluating each primitive effect with runEffect and interpreting each call to Redux with the store.
runEffectthe function to evaluate a serialized side effect (may return a promise for asynchronous effects)storethe current Redux storeshipthe ship to execute
Example
To run Redux Ship with a Redux store store, you can do:
Ship.run(runEffect, store, ship);
where runEffect is your function to evaluate your side effects:
export type Effect = {
type: 'HttpRequest',
url: string,
};
export async function runEffect(effect: Effect): Promise<any> {
switch (effect.type) {
case 'HttpRequest': {
const response = await fetch(effect.url);
return await response.text();
}
default:
return;
}
}
simulate
simulate<Effect, Commit, State, A>(
ship: Ship<Effect, Commit, State, A>,
snapshot: Snapshot<Effect, Commit>
): Snapshot<Effect, Commit>
Simulates a ship in the context of a snapshot and returns the snapshot of the simulation. A simulation is a purely functional (with no side effects) execution of a ship. Useful for regression testing.
shipthe ship to simulatesnapshota snapshot of a previous execution of the ship
To simulate a ship, we need a snapshot of a previous live execution because there are many ways to execute a ship. For example, if the ship starts an API request, we do not know which API answer to provide. In this case, we use a snapshot which already contains an API answer. Since the simulation always gives the same API answers as in the snapshot, the ship should behave the same as when its snapshot was taken. In particular, the result of simulate should be equal to snapshot, unless the implementation of ship had a regression since its snapshot was taken.
Example
In a unit test of a controller:
expect(Ship.simulate(control(action), snapshot)).toEqual(snapshot);
snap
snap<Effect, Commit, State, A>(
ship: Ship<Effect, Commit, State, A>
): Ship<Effect, Commit, State, {
result: A,
snapshot: Snapshot<Effect, Commit>
}>
Returns a ship behaving as ship but taking the snapshot of its own execution.
shipthe ship to take in picture
Example
To take the snapshot of a ship:
const {result, snapshot} = yield* Ship.snap(ship);
To take the snapshot of a controller (the result should always be undefined):
const {snapshot} = yield* Ship.snap(control(action));