What is a Ferp Effect?


A side-effect, or simply effect, is a way of taking outside data into the next application update loop. Effects can be simple strings, objects, or calls to some of the Core Effects. In the case of using a core effect, your effect will probably use outside data, and as a result, will be an impure function.

You effects may look something like:

import { effects } from 'ferp';
const exampleEffects = {
effect1: 'I am an effect',
effect2: 1234,
effect3: { type: 'I am an effect, too' },

Ultimately, you will want to define your effects to be consistent to process, and meaningful to you as an application developer. If your effects are just instructions with no data, strings may be the easiest to manage. If you have data, I'd recommend an object with some sort of descriptor field, like type.

Effect Functions

Although an effect can be anything, encapsulating them in a function often will make it easier to design composable effects. Let's look at this effect signature:

const delayEffect = (timeout, nextEffect) => { /* ... */ }

This effect waits timeout milliseconds, then triggers the nextEffect effect. The nextEffect may be some other effect that triggers a series of other effects. If this were not a function, we may not get the flow control we expect, but in a function, we guarantee that the nextEffect is ran only when the parent effect is ready to run it. Another problem is that if nextEffect is a deferred promise outside of a function, then running that same effect again (or forwarding it internally) will instantly resolve. Let's look at the good and bad version of the delay effect:

import { effects } from 'ferp';
// ** The not-a-function effect version (BAD)
const delayEffect = (timeout, nextEffect) => effects.defer(
new Promise((resolve) => {
setTimeout(resolve, timeout, nextEffect);
// Why it's bad
const myEffect1 = delayEffect(1000, { type: 'post-delay' }); // will work, but...
const myEffect2 = delayEffect(1000, { type: 'post-delay', now: Date.now() }); // won't, because now is set immediately and no after the delay
const myEffect3 = delayEffect(1000, effects.none); // doesn't work at all
// ** The function effect version (GOOD)
const delayEffect = (timeout, nextEffect) => effects.defer(
new Promise((resolve) => {
setTimeout(() => resolve(nextEffect()), timeout);
// Why it's good
const myEffect1 = delayEffect(1000, () => ({ type: 'post-delay' })); // works.
const myEffect2 = delayEffect(1000, () => ({ type: 'post-delay', now: Date.now() }); // works, too
const myEffect3 = delayEffect(1000, effects.none); // also works, yay!

How to use an Effect

All updates to your application must return a [state, effect] tuple from your update function - if you're not sure about that, check out the boiler plate documentation. When updating your state, you may want to run an operation outside of your app - this could mean setting a delay, waiting on a promise, or queuing a follow-up message. You can even perform multiple effects in a batch. Of course, if you don't need an effect, you can specify that you don't need one with none, but you must tell ferp that explicitly. To run an effect, return your state along with your effect directives.

When NOT to use an Effect

Effects are great at making your application do the right thing, but are there times when you shouldn't use an effect? Yes, effects should not be used for uncontrolled repeated events. For instance, you shouldn't primarily use effects to consume DOM events, like a button click.

A sign that you are abusing/misusing effects is if you are using many effect.defer calls. Deferred effects are great for a few edge cases, but over-use can be a sign that you really want a subscription.