Source as dependency
There are cases in which the initialization of a source depends on another source. In this document we are going to explore some of the valid (and invalid) patterns to relate Pure and non-Pure sources.
Between Pure sources
In this example we are using the classic composition to create a Container of an Item. The Container must be defined last.
tsx
const [, item] = usePureSource(() => new Item());
const [, container] = usePureSource(
// ✅ classic composition, the Item is defined before the Container.
() => new Container(item),
[item]
);
Note that the opposite is Not a valid pattern. If we define the Container earlier, we are not able to insert the Item without a side-effect.
tsx
const [, container] = usePureSource(() => new Container());
// ❌ The only way to insert the Item is with a mutation.
const [, item] = usePureSource(() => {
const item = new Item();
// ❌ This is a side-effect!
container.add(item);
return item;
}, [container]);
Another possible pattern is to create the Items dynamically, and use them inside children components. In this example we use a contract
.
tsx
const ListComponent = () => {
const [useSnapshot, list] = usePureSource(
() => new List(),
// Subscribes to the events of adding/removing items.
onListChange
);
// ✅ Derives the children inside useSnapshot.
const children = useSnapshot(() =>
list.map((item) => <ItemComponent key={item.id} item={item} />)
);
// Dynamically creates the items.
const addItem = (item) => list.add(item);
// Rest of the component...
};
tsx
const ItemComponent = ({ item }) => {
// ✅ The List is Not mutated inside the init function.
const [useSnapshot] = usePureSource(() => item, [item]);
// Rest of the component...
};
Between Pure and non-Pure sources
In this example we are using the classic composition to create a Container of an Item. The Container must be the non-Pure source and must be defined last.
tsx
const [, item] = usePureSource(() => new Item());
const [, getContainer] = useSource(
// ✅ classic composition, the Item is defined before the Container.
() => [new Container(item)],
[item]
);
Note that the opposite is Not a valid pattern. If we define the Item as non-Pure and the Container as Pure, we are not able to insert the Item without a side-effect.
tsx
const [, getItem] = useSource(() => [new Item()]);
const [, container] = usePureSource(
() =>
new Container(
// ❌ This is a side-effect!
getItem()
),
[getItem]
);
Another possible pattern is to create the Item components dynamically, and generate an Item inside each of them.
tsx
const ListComponent = () => {
const [, getList] = useSource(() => [new List()]);
const [items, setItems] = useState([1, 2, 3]);
// Dynamically generates the items.
const children = items.map((key) => (
<ItemComponent key={key} getList={getList} />
));
// Rest of the component...
};
tsx
const ItemComponent = ({ getList }) => {
const [, item] = usePureSource(() => new Item());
// ✅ Add the item only inside an effect.
useEffect(() => {
const list = getList();
// Mutates the list.
list.add(item);
// Returns a cleanup function.
return () => list.remove(item);
}, [getList, item]);
// Rest of the component...
};
Between non-Pure sources
In this example we are using the classic composition to create a Container of an Item.
tsx
const [, getItem] = useSource(() => [new Item()]);
const [, getContainer] = useSource(
// ✅ classic composition.
() => [new Container(getItem())],
[getItem]
);
We can do the same in the opposite order.
tsx
const [, getContainer] = useSource(() => [new Container()]);
const [, getItem] = useSource(() => {
const item = new Item();
// ✅ We can have side-effects here.
const container = getContainer();
container.add(item);
return [item];
}, [container]);
Another possible pattern is to create the Item components dynamically, and generate an Item inside each of them.
tsx
const ListComponent = () => {
const [, getList] = useSource(() => [new List()]);
const [items, setItems] = useState([1, 2, 3]);
// Dynamically generates the items.
const children = items.map((key) => (
<ItemComponent key={key} getList={getList} />
));
// Rest of the component...
};
tsx
const ItemComponent = ({ getList }) => {
const [, getItem] = useSource(() => [new Item()]);
// ✅ Add the item only inside an effect.
useEffect(() => {
const list = getList();
// Mutates the list.
list.add(item);
// Returns a cleanup function.
return () => list.remove(item);
}, [getList, item]);
// Rest of the component...
};
Inherit dependencies
The stability of useSnapshot
and getSource
is defined by the source dependency list
. This means that if the dependencies don't change, these functions are guarantee to be stable
. You can use these functions as dependencies as you would with any other variable.
tsx
const [, getItem] = useSource(
() => [new Item(dep1, dep2)],
// Dependencies of item
[dep1, dep2]
);
const [, getContainer] = useSource(
() => [new Container(getItem())],
// ✅ Correctly defines "getItem" as a dependency. As usual, this implicitly
// inherit the dependencies "dep1" and "dep2" from the item.
[getItem]
);