Documentation
Complete guide to using Tilia for simple and fast state management in TypeScript and ReScript applications.
Installation
Use the canary version (not yet released because the API might still change, but it is stable):
npm install tilia@canary
Goals and Non-goals
The goal with Tilia is to be minimal and fast while staying out of the way. A special effort was made to keep the API simple and intuitive, while supporting best practices (type safety, proper management of transitive states, etc).
We haven't measured the performance of the library yet, but everything was designed to make it as fast and lightweight as possible. If someone wants to help us benchmarking, we'd be happy to add this information to the documentation.
Non-goal Tilia is not a framework.
API Reference
connect
Connect an object or array to the forest so that it can be observed.
We use the default context. If we want to have multiple contexts, we can create
them with make
which will return a new isolated set of functions
(connect, observe, etc.).
import { connect } from "tilia"
const alice = connect({
name: "Alice",
birthday: dayjs("2015-05-24"),
age: 10,
})
Alice can now be observed. Who knows what she will be doing?
observe
Observe and react to changes. Every time the callback is run, tilia registers which values are read in the connected objects and arrays and will notify the observer if any of these values changed.
import { observe } from "tilia"
observe(() => {
console.log("Alice is now", alice.age, "years old !!")
})
📖 Important Note: If you have mutations of observed values in the
observe
callback,
the callback will be immediately re-run (to support state machines).
Now every time alice's age changes, the callback will be called.
signal
Use signal
to represent a value that changes, such as a date, a number, a
variant or even the app as it goes through its life cycle.
import { signal } from "tilia"
// Ending signals with '_' can be useful to avoid name collisions and to recognize them.
const [now_, setNow] = signal(dayjs())
setInterval(() => setNow(dayjs()), 1000 * 60)
// To read the value, we simply do
console.log(now_.value)
💡 Pro tip: The signal
function is just syntax sugar
on top of connect
.
function signal<a>(value: a): Signal<a> {
const s = connect({ value })
const set = (v) => (s.value = v)
return [s, set]
}
Our app knows the date ! Let's see how we can update Alice's age from this value.
computed
Compute a value from other connected objects.
import { computed } from "tilia"
const alice = connect({
name: "Alice",
birthday: dayjs("2015-05-24"),
age: computed(() => now_.value.diff(alice.birthday, "year")),
})
📖 Note: The computed
only recomputes or notifies when
needed. Notification happens if there are observers, and if the computed value changes.
Nice, the age updates automatically, Alice can grow older. Let's wish her a happy birthday when this happens.
observe to update
You can also use observe
to
update a value from the current value and other connected objects.
import { observe, signal } from "tilia"
const [userAge_, setUserAge] = signal(alice.age)
observe(() => {
if (alice.age !== userAge_.value) {
setUserAge(alice.age)
console.log("Alice is now", alice.age, "years old !!")
console.log("🥳🩷 Happy Birthday Alice !! 🩷🥳")
}
})
This pattern lets us create state machines that are very useful for initialisation or other complex states. See app.ts for an example.
💡 Pro tip: Mutating an observed value in observe
will
retrigger the callback.
We feel good with our app now. It only wishes a happy birthday when the age changes, it knows the age of Alice.
Maybe we can use this to manage access to a social media ?
derived
Derive a signal from other connected objects (other signals or values).
import { derived } from "tilia"
const socialMediaAllowed_ = derived(() => userAge_.value > 13)
💡 Pro tip: derived
is syntactic sugar
on top of computed
.
function derived<a>(fn: () => a): Signal<a> {
return connect({ value: computed(fn) })
}
useTilia (React Hook)
The useTilia
hook registers the component to observe state changes,
automatically triggering a re-render whenever any observed value updates.
This hooks makes the component's render function act like an observe
callback.
Installation
npm install @tilia/react@canary
import { useTilia } from "@tilia/react"
function App() {
useTilia()
if (socialMediaAllowed_.value === true) {
return <SocialMediaApp />
} else {
return <NormalApp />
}
}
Main Features
Examples
You can check the todo app for a working example using TypeScript.
Look at tilia tests for working examples using ReScript.
Changelog
2025-05-24 2.0.0 (canary version)
- Moved core to "tilia" npm package.
- Changed
make
signature to build tilia context. - Enable forest mode to observe across separated objects.
- Add
computed
to compute values in branches. - Moved
observe
into tilia context. - Added
signal
, andderived
for FRP style programming. - Simplify
useTilia
signature.
See the full changelog in the README for previous versions.