Skip to main content

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.