Dynamic models
At the moment dynamic models are still under development, their API may change over time. This article is for informational purposes only, and we strongly do not recommend using this functionality in production.
NOT RECOMMENDED FOR PRODUCTION USE.
Currently effector does not support creating units dynamically; units must be defined statically at the module level. If units are created at runtime, a memory leak will occur because units will remain in the graph forever. Although you can try using withRegion, createNode, and clearNode, this requires developer expertise, since these are low-level APIs, and moreover you will have to track the unit lifecycle yourself, which can be a headache.
Therefore, for cases where dynamics were needed, key-value stores were used, storing objects where keys were identifiers and values were states, for example:
import { createStore, createEvent } from "effector";import { useStoreMap } from "effector-react";
type Item = { id: string; count: number };const $items = createStore<Record<string, Item>>({});
const addItem = createEvent<Item>();const removeItem = createEvent<string>();
$items.on(addItem, (state, item) => ({ ...state, [item.id]: item,}));
$items.on(removeItem, (state, id) => { const copy = { ...state }; delete copy[id]; return copy;});In the UI, useStoreMap was used to subscribe only to the part that corresponds to id to get the data:
import { $items, addItem, removeItem } from "./model";import { useStoreMap, useUnit } from "effector-react";
function Counter({ id }: { id: string }) { const item = useStoreMap({ store: $items, keys: [id], fn: (state, [key]) => state[key], });
const [onAddItem, onRemoveItem] = useUnit([addItem, removeItem]);
if (!item) return null;
return ( <div> <span>{item.count}</span> <button onClick={() => onAddItem({ id, count: item.count + 1 })}>+</button> <button onClick={() => onRemoveItem(id)}>Delete</button> </div> );}Although this approach works, it is not very convenient, especially if the structure is more complex than in this example.
Models introduce a new way to work with dynamic states, allowing you to create model instances on the fly that have their own states and logic.
Setup and working with models
Currently models are implemented in a separate repository and available as a separate package:
npm install @effector/modeland also a package for React integration:
npm install @effector/model-reactIn the root of the repository you can find the apps directory with examples of model usage in applications with near-real functionality. In this article we will just get familiar with the API and what dynamic models look like.
Model API
Dynamic models introduce a set of new APIs:
-
keyvalβ an operator that creates a collection of model instances, where each element is identified by a key. Dynamic creation and deletion of model instances happens viakeyval. It can also be used inside anotherkeyvalfor nested structures. It expects a callback that returns an object with the following properties:stateβ the model state, an object containing stores or anotherkeyvalmodel. One of the properties must also serve as the model keykeyβ the model key, i.e. its unique identifierapiβ an optional object with events or effects for working with the modelonMountβ an optional event or effect triggered when a model instance is createdoptionalβ an optional array of strings representing non-required fields of the model at creation
Example:
export const restaurantsList = keyval(() => { const $name = createStore(""); const $description = createStore(""); const $category = createStore<string[]>([]);
const dishesList = keyval(() => { const $name = createStore(""); const $description = createStore(""); const $price = createStore(0); const $additives = createStore<Additive[]>([]);
return { key: "name", state: { name: $name, description: $description, price: $price, additives: $additives, }, optional: ["additives"], }; });
return { key: "name", state: { name: $name, description: $description, category: $category, dishes: dishesList, }, api: { addDish: dishesList.edit.add, removeDish: dishesList.edit.remove, }, optional: ["category", "dishes"], };});Now using restaurantsList we can add, update, or remove model instances at runtime.
const addRestaurant = createEvent();
sample({ clock: addRestaurant, fn: () => ({ name: "Starbucks", description: "American corporation and the largest coffeehouse chain in the world", }), target: restaurantsList.edit.add,});lensβ lens is needed to dive inside akeyvalfor working with data. For example, with nestedkeyvalwe can access data or API from the very top to the very bottom:
const menuItemIdLens = lens(orderKeyval).item(orderId).menuItemId;const foodDescLens = lens(restaurantKeyval).item(restId).menu.item(menuItemIdLens).description;At the moment the lens API is still being refined and may differ from what is shown in the repository examples.
In addition to the main effector package, there is also an API for effector-react to conveniently work with models in React:
useEntityList(keyval, View)β hook that takeskeyvalas the first argument and a component as the second. Iterates over all keys in the collection and for each creates anEntityProvider, passingViewinto it. Simply put, itβs a way to render a list and later work with other hooks without passingid.useEntityItem(keyval, key?)β returns an entity byidin akeyvalcollection. Ifkeyis explicitly provided, it searches for the element by this key, if not provided, it tries to get it from the nearestEntityProvider.useItemApi(keyval, key?)β returns the entity API object for working with it.useEditItemField(keyval, key?)β returns an object with functions for updating each model field. Ifkeyis explicitly provided, it searches for the element by this key, if not provided, it tries to get it from the nearestEntityProvider.useEditKeyval(keyval)β returns an object of methods for modifying the model, such as add, delete, or update.
const { add, map, remove, replaceAll, set, update } = useEditKeyval(ordersList);Related API and docs
-
API
clearNodeβ Method for destroying unitswithRegionβ Method to set region for units
-
Articles