Comment on page
What is a Ferp Action?
Actions are the only way to initiate new state changes and side-effects in Ferp. State changes must be complete and immutable, and side effects must always be declared, even if there are no side-effects. Actions are always pure functions that return
[state, sideEffect]
, and pure meaning it does not use any data not immediately accessible to the action. On the contrary, an invalid action would use things like Math.random()
or Date.now()
, which are external to the action function.Plain, or naked, actions are actions that need no parameters. For instance, a naked action could be something that only updates a single state property by incrementing it by 1. Let's see what that looks like:
import { effects } from 'ferp';
const IncrementCounterByOne = (state) => [
{ ...state, counter: state.counter + 1 },
effects.none(),
];
Depending on the shape of your state, you may find naked actions to be very fitting.
There are two ways to call an action, and here they are:
import { app, effects } from 'ferp';
const IncrementCounterByOne = (state) => [
{ ...state, counter: state.counter + 1 },
effects.none(),
];
const dispatch = app({
init: [
{ counter: 0 },
effects.act(IncrementCounterByOne), // run an action via effect
],
});
dispatch(IncrementCounterByOne); // run an action via dispatch
Action builders are actions that can take parameters. This may mean a value can be passed from user input, or from some other external side-effect. Using the previous counter example, here's what it would look like if it took external input for the increment amount:
import { effects } from 'ferp';
const IncrementCounterByN = (n) => (state) => [
{ ...state, counter: state.counter + n },
effects.none(),
];
Similar to naked actions, there are two ways to run this action in your application:
import { app, effects } from 'ferp';
const IncrementCounterByN = (n) => (state) => [
{ ...state, counter: state.counter + n },
effects.none(),
];
const dispatch = app({
init: [
{ counter: 0 },
effects.act(IncrementCounterByN(10)), // run a builder action via effect
],
});
dispatch(IncrementCounterByN(999)); // run a builder action via dispatch
If you're not sure how the builder works, this is just a function that returns a function. Here's probably a more familiar version to read:
import { effects } from 'ferp';
function IncrementCounterByNBuilder(n) {
return function IncrementCounterByNAction(state) {
return [
{ ...state, counter: state.counter + n },
effects.none(),
];
};
};
This is called a closure, and it's used in Javascript to encapsulate a scope. For instance, the variable
n
is available in the inner function (IncrementCounterByNAction
), but n
is not available outside of this closure (IncrementCounterByNBuilder
).To make this easier to understand, once we have this builder that returns an action, internally, Ferp does something like this:
// You call...
dispatch(IncrementCounterByNBuilder, 999);
// Ferp does...
const yourAction = IncrementByNBuilder(999);
dispatch(yourAction);