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 oneffector
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
-
Stale state
If we somehow by accident getundefined
in of the host stores, recipient stores will not have update, thus producingstale 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
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
isforward
which makes host store fire on particular route, e.g. when we go todollar
, 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.