Skip to content

Instantly share code, notes, and snippets.

@Tauka
Last active December 26, 2019 11:16
Show Gist options
  • Save Tauka/c7b27740e70ad7b4fe41dd64552bdb23 to your computer and use it in GitHub Desktop.
Save Tauka/c7b27740e70ad7b4fe41dd64552bdb23 to your computer and use it in GitHub Desktop.
Loose coupling features with `effector`

Loosely coupled features with effector

Motivation

Loose coupling and high reusability of view + businesss logic

Достоинства

  • High reusability of BL
    Several disconnected features can utilize another feature with all it has inside. We can compose features on effector level

  • BL abstraction
    We don't have to know implementation details of feature to compose it. As long as it works, we can be sure it will work if we satisfy its interface

  • Decomposing complex features with effector We can split large features into smaller and reusable ones, and better implement Separation of Concerns

Problems

  • Stale state
    If we somehow by accident get undefined in of the host stores, recipient stores will not have update, thus producing stale state

  • Need in activation
    When dynamically connecting stores together, host stores will not fire update by themselves, thus in order for recipient stores to have actual state, you need to fire updates

  • Non-obvious connection
    When connecting features this way, it may not be immediately clear that they are actually connected

Example

Example

We have 3 features in this example, dollar, euro, calculator. calculator is universal feature, which means it can convert both dollar and euros.

// calculator/model.js

import { createStore, combine } from "effector";

export const $exRate = createStore(0);
export const $input = createStore(0);

export const $kzt = combine($input, $exRate, (input, exRate) => {
  return parseInt(input, 10) * exRate;
});

export const $calcUI = combine({
  exRate: $exRate,
  input: $input,
  kzt: $kzt
});

Calculator only depends on its stores, only thing it knows is how to render its own stores (it doesn't know what is happening outside)

import React from "react";
import { useStore } from "effector-react";

import { $calcUI } from "./model";

const Calculator = () => {
  const { exRate, kzt } = useStore($calcUI);

  return (
    <div>
      <div> Exchange rate: {exRate} </div>
      <div> KZT: {kzt} </div>
    </div>
  );
};

export default Calculator;

Now we want to reuse that logic within the context of dollars and euros, and all we have to do it forward required values

// euro/model.js

import { createStore, createEvent } from "effector";
import { $exRate, $input } from "../calculator/model";
import { routeForward } from "../../utils";

export const $euroInput = createStore("0");
const $euroRate = createStore(436);
export const evChangeInput = createEvent();

$euroInput.on(evChangeInput, (_, val) => val);

routeForward({
  from: $euroInput,
  to: $input,
  route: "euro"
});

routeForward({
  from: $euroRate,
  to: $exRate,
  route: "euro"
});

We made calculator convert euros!

import React from "react";
import { useStore } from "effector-react";

import Calculator from "../calculator/Calculator";
import { evGoTo } from "../../router";
import { $euroInput, evChangeInput } from "./model";

const Euro = () => {
  const input = useStore($euroInput);

  return (
    <div>
      <h1> Euro to KZT </h1>
      <input onChange={ev => evChangeInput(ev.target.value)} value={input} />
      <Calculator />
      <button onClick={() => evGoTo("dollar")}> Dollar </button>
    </div>
  );
};

export default Euro;

Same thing works with dollars.

Note: routeForward is forward which makes host store fire on particular route, e.g. when we go to dollar, actual values will be sent to recipient stores

export const routeForward = ({ from, to, route }) => {
  const routeFrom = sample(from, evGoTo.filter({ fn: path => path === route }));
  
  // forward from value when you go to path
  forward({
    from: routeFrom,
    to
  });

  // forward from when from itself is updated
  forward({
    from: from,
    to
  });
};

First forward will send actual values of stores on particular route

Second forward is regular one, it will send new updates as they emerge

This article shows how we can reuse particular logic written in effector in various contexts. You don't have to know how this logic is implmeneted to use it, all you have to do is fulfill requirements. Even though, this article showed you how this pattern can be implemented, it is not meant to be used as is, its rather familiarizes you with mental model of pattern. You may have different implementation of this pattern.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment