Core Effects

The basic effects that Ferp provides to help you build better software

effects.none()

The none effect is very simple, there is no effect. Use this effect to tell your app there is nothing to do after an update. All arguments passed to it are ignored, and your update function will not be called.

effects.defer(promiseLike)

A deferred promise is a great way to connect things like timers, http requests, and other asynchronous code to your update loop. There is a hitch, though - deferred effects should never throw an error. This means that if your effect has the chance of causing a run-time error, it must be caught inside your effects.defer call, and an alternative effect should be returned. Here is an example of a bad deferred effect:

badDeferredEffect.js
import { effects } from 'ferp';
const fetchEffect = (url, options, okEffect) => effects.defer(
fetch(url, options)
.then(response => response.json())
.then(okEffect)
);
// Usage:
fetchEffect(
'https://jsonplaceholder.typicode.com/posts',
undefined,
(json) => ({ type: 'httpSuccess', json }),
);

What happens if this fetch fails? It will throw an exception, and crash your app. To counter-act this, provide an explicit error effect:

goodDeferredEffect.js
import { effects } from 'ferp';
const fetchEffect = (url, options, okEffect, errorEffect = effects.none) => effects.defer(
fetch(url, options)
.then(response => response.json())
.then(okEffect)
.catch(errorEffect)
);
// Usage:
fetchEffect(
'https://jsonplaceholder.typicode.com/posts',
undefined,
json => ({ type: 'httpSuccess', json }),
error => ({ type: 'httpFailure', error }),
);

This provides your app with a reasonable way to manage the error without being opinionated.

The defer effect can also be used to wait a single tick, so something like defer(act(YourAction))will allow the application to finish it's current tick and other queued work.

And one last ease of use, you can pass a Promise constructor function directly, like defer((resolve, reject) => {}) to avoid the overly verbose defer(new Promise((resolve, reject) => {})) version, but both are valid.

effects.thunk(() => effect)

Thunks are a way to prevent side-effect code from running until the effects manager processes the effect. This is great for isolating your effects, like a call to console.log , but can also be used to defer a promise or a desired synchronous batch behavior. A great example use case for a thunk is chaining a series of timeouts. Consider the following example:

badDelayEffect.js
import { effects } from 'ferp';
const delay = (milliseconds, effect) => effects.defer(new Promise((resolve) => {
setTimeout(() => resolve(effect()), milliseconds);
}));
delay(1000, () => effects.batch([
'first timer done',
delay(1000, () => 'second timer done'),
]));

You might expect something like /* wait 1 second */, 'first timer done', /* wait 1 second */, 'second timer done', but in reality, it will be /* wait 1 second */, 'first timer done', 'second timer done'. This is because the promises are kicked off immediately, before the effect manager handles them. In some cases, like non-dependent http requests, this is great, but for chaining timers, it is not. The solution, as you might have guessed, is wrapping our delay effect in a thunk, just like this:

goodDelayEffect.js
import { effects } from 'ferp';
const delay = (milliseconds, effect) => effects.thunk(() => effects.defer(new Promise((resolve) => {
setTimeout(() => resolve(effect()), milliseconds);
})));
delay(1000, () => effects.batch([
'first timer done',
delay(1000, () => 'second timer done'),
]));

Now each delay waits until it is called, with a 1 second timeout before each message. Thunks are a great tool for effect flow control.

effects.batch(arrayOfEffects)

Batch is the other powerful flow control tool for effects. Often times, an update will only need to do a single effect, but this is not the only case. Batching effects gives you the opportunity to run multiple side effects from a single action or dispatch, and in order.

effects.act(actionFunction, alias)

Sometimes as a result of an action, or inside another effect, you'll want to invoke an action to update the state. Actions can take two basic forms, and as a result, effects.act has two ways of being called.

The first form of an action is one that purely uses state. That means it takes no values from any other place, and should always be a pure function. Let's take a look:

import { effects } from 'ferp';
const PureStateActionIncrementState = (state) => [state + 1, effects.none()];
effects.act(PureStateActionIncrementState);

These may be effects that are constant, such as the + 1 implementation seen above.

The second form of action takes values from another source. For instance, you may use these to bring information from the outside work (an effect) into your application state. These types actions look like this:

import { effects } from 'ferp';
const ValueActionIncrementState = (amount) => (state) => [state + amount, effects.none()];
effects.act(ValueActionIncrementState(1), 'ValueActionIncrementState');

While these two examples achieve the same result, the second example is able to use outside world information to update your application.