Extensions
When you feel like you're repeating yourself too much or just want to have standard ways to change the value of a superstate, then it's time for extensions!
Let's suppose you have a count
superstate:
const count = superstate(0)
Overriding the value of count
is very straightforward:
count.set(1)
Increasing the value of it is very straightforward as well:
count.set((prev) => prev + 1)
But wouldn't it be magical if you could do the following?:
count.sum(1)
The example above has a few benefits:
- It's easier to read,
- It's easier to write,
- It's easier to test,
- It's easier to maintain,
- It's less verbose,
- Separates business logic from the implementation.
Creating an extension
From the example above, to create an extension that sums your count
:
const count = superstate(0).extend({
sum({ set }, amount: number) {
set((prev) => prev + amount)
},
})
count.sum(1) // `count.now()` will log 1
count.sum(1) // `count.now()` will log 2
count.sum(5) // `count.now()` will log 7
Let's understand what's happening here:
sum({ set }, amount: number) {
publish(prev => prev + amount)
}
sum
is the name of your extension.
{ set }
is a superstate
object, which exposes the set
method.
danger
The first parameter of an extension should always be reserved to the superstate
object.
amount: number
a custom parameter of the .sum()
function. This is completely up to us to define and we can have as many parameters
as we like. For example:
sum({ set }, x: number, y: number, z: number) {
set(x + y + z)
}
info
The ": number
" piece is TypeScript. You can omit it when writing vanilla JavaScript.
set(prev => prev + amount)
The regular set
function exposed by a superstate
object.
Computed values
Given the example below,
const user = superstate({ firstName: 'John', lastName: 'Doe' })
It's exhaustive to always be concatenating the first and last name of the user:
// logs John Doe
console.log(`${user.now().firstName} ${user.now().lastName}`)
Extensions got you covered by allowing computed values. Let's see how it works:
const user = superstate({ firstName: 'John', lastName: 'Doe' }).extend({
fullName,
})
function fullName({ now }) {
const _user = now()
return `${_user.firstName} ${_user.lastName}`
}
console.log(user.fullName()) // logs John Doe
Just to give you another use-case for computed values:
const money = superstate(100).extend({ formatted })
function formatted({ now, draft }) {
return `$${now()}`
}
console.log(money.formatted()) // logs $100
Side effects
You can also handle side effects within your extension's scope:
const count = superstate(0).extend({
sum({ set, now }, amount: number) {
const next = now() + amount
set(next)
// Feel free to do whatever you like.
alert(`count has changed to ${next}.`)
},
})
Note that by doing the above, whenever you call .sum()
, the alert()
will be called as wellโmeaning you are the one in charge
for managing your side effect(s) lifecycle.