What is a Ferp Subscription?

The Basics

Now that you understand ferp effects, subscriptions are the next key thing to understand. An effect is a great way to grab a single bit of external data, but subscriptions are a way to grab that information over time. Some great examples would be browser DOM events like keyboard presses, web socket listeners, or intervals that you want to react to. Your subscriptions will look something like this:

subscriptionBoilerplate.js
import { app, effects } from 'ferp';

const yourIntervalSubscription = (dispatch, interval, intervalAction) => {
  const handle = setInterval(
    () => dispatch(intervalAction),
    interval,
  );
  
  return () => {
    clearInterval(handle);
  };
};

// And to use it:

app({
  init: [
    { someBooleanValue: false },
  ],
  subscribe: (state) => [
    [yourSubscription, 1000, effectFunctionA],
    [yourSubscription, 3000, effectFunctionB],
    state.someBooleanValue && [yourSubscription, 1000, effectFunctionC],
  ],
});

There are three very important things to understand about subscriptions from the demo.

  1. Subscriptions always clean up after itself. The last function in the chain is the remove function. This is called either when your subscription is removed from the subscribe array, or when the application is detached. Not cleaning up timers, listeners, and other callbacks ran by third parties can result in unpredictable code.

  2. Subscriptions are created via array. The array consists of [aSubscriptionFunction, ...argumentsForSubscription]. This allows subscriptions to be memoized internally, and also for the subscription to receive dispatch in the second method.

  3. Subscriptions are a function of state, thus can be disabled by state changes. Every call to update will reactively call subscribe. One way to disable subscriptions is through boolean toggles. If the value is truthy, the subscription will be handled by the app, and if it is falsy, it will be cleaned up and removed.

  4. Subscriptions cannot destroy themselves. A subscription can signal to the application that it's done, but it cannot otherwise forcibly destroy itself.

The Subscribe Array

As noted above, subscriptions are an array of arrays. The top level array is the list of subscriptions. The secondary arrays are the signatures; the first signature item is the subscription function, and the following are its arguments.

We also learned the in the above section that subscriptions can be disabled using boolean operations. We can also disable subscriptions by removing them completely from the array. You'll likely run into this situation if you have an array of items in state that require subscriptions:

stateArrayForSubscriptions.js
import { app, effects } from 'ferp';

const clickSubscription = (dispatch, selector, clickAction) => {
  const elements = Array.from(
    document.querySelectorAll(selector),
  );
  
  const onClick = (event) => {
    dispatch(clickAciton(event));
  }
  
  elements.forEach((element) => {
    element.addEventListener('click', onClick);
  });
  
  return () => {
    elements.forEach((element) => {
      element.removeEventListener('click', onClick);
    });
  };
};

const ClickAction = (event) => (state) => [
  { ...state, mouse: { x: event.clientX, y: event.clientY } },
  effects.none(),
];

const initialState = {
  mouse: { x: 0, y: 0 },
  domQuerySelectors: ['button.with-click', 'a.with-click'],
};

app({
  // init: [],
  
  subscribe: (state) => [
    ...state.domQuerySelectors.map(selector => (
      [clickSubscription, selector, myEffectGenerator]
    )),
  ]
});

As the application runs, update may or may not change the domQuerySelectors array, which will result in some subscriptions being dropped, which will clean them up as expected.

When to use a subscription

A subscription is something that gives you output uncontrollably. By this, I mean things like a DOM event, and more specifically, let's say a button click. Your application cannot control when a button click can happen, but it's probably data that you want your application to consume. Typically if the interface your using has a pattern similar to addEventListener(a)/removeEventListener(a), on(a)/off(a), or const unsubscribe = thing.listen(a), that's a good sign that a subscription is a good fit.

Last updated