Third-Party Libraries and Objects

Handling Uncontrolled Third Party Objects

Not everything in the world is functional and immutable, and occasionally your application needs to manage these things. Ferp provides effects and subscriptions for handling these types of systems, but it's not always obvious which one to use and how to implement it.

Rebuild objects by storing builder values

In the example of firebase, you may have a document reference that is constructed by:

firebase.database(app).ref().collection('foo').document('bar')

While you could store the firebase ref object in state, having a serializable

effects/firebase.js
export const firebaseReadFx = (path, afterReadAction) => effects.defer((resolve) => {
  firebase.database(app).ref(path).snapshot((value) => {
    resolve(effects.act(afterReadAction, value));
  });
});

// usage: firebaseReadFx('foo/bar', actionThatAcceptsValue)

Event-driven objects

If you have objects like websockets, those are perfect for subscriptions. Here's how I would set it up:

import { app, effects } from 'ferp';

const INITIAL_STATE = {
  websocket_url: 'wss://echo.websocket.org',
  websocket: null,
  messages: [],
};

const WebsocketSendFx = (data, websocket) => effects.thunk(() => {
  if (websocket) websocket.send(data);
  
  return effects.none();
});

const WebsocketSend = data => state => [
  state,
  WebsocketSendFx(data, state.websocket),
];

const OnConnected = (websocket) => (state) => [
  { ...state, websocket },
  effects.act(WebsocketSend, 'hello, world'),
];

const OnDisconnected = (state) => [
  { ...state, websocket: null },
  effects.none(),
];

const OnMessage = (message) => (state) => [
  { ...state, messages: state.messages.concat(message) },
  effects.none(),
};

const WebsocketSubscription = (dispatch, websocketUrl, ConnectAction, DisconnectAction, MessageAction) => {
  const websocket = new WebSocket(websocketUrl);
  
  const onOpen = () => dispatch(ConnectAction(websocket));
  const onClose = () => dispatch(Disconnect);
  const onMessage = ({ data }) => dispatch(MessageAction(data));
  
  websocket.addEventListener('open', onOpen);
  websocket.addEventListener('close', onClose);
  websocket.addEventListener('message', onMessage);
  
  return () => {
    websocket.removeEventListener('open', onOpen);
    websocket.removeEventListener('close', onClose);
    websocket.removeEventListener('message', onMessage);
  
    websocket.close();
  };
};

const dispatch = app({
  init: [INITIAL_STATE, effects.none()],
  subscribe: state => [
    state.websocketUrl && [
      WebsocketSubscription,
      state.websocketUrl,
      OnConnect,
      OnDisconnect,
      OnMessage,
    ],
  ],
});

Last updated