Overmind also provides a functional API to help you manage complex logic. This API is inspired by asynchronous flow libraries like RxJS, though it is designed to manage application state and effects. If you want to create and use a traditional action “operator style” you define it like this:
import { Operator, mutate } from 'overmind'
export const changeFoo: <T>() => Operator<T> = () =>
mutate(({ state }) => {
state.foo = 'bar'
})
The mutate function is one of many operators. Operators are small composable pieces of logic that can be combined in many ways. This allows you to express complexity in a declarative way. You typically use the pipe operator in combination with the other operators to do this:
import { Operator, pipe, debounce } from 'overmind'
import { QueryResult } from './state'
import * as o from './operators'
export const search: Operator<string> = pipe(
o.setQuery(),
o.filterValidQuery(),
debounce(200),
o.queryResult()
)
Any of these operators can be used with other operators. You can even insert a pipe inside an other pipe. This kind of composition is what makes functional programming so powerful.
async
This operator runs if any of the previous operators throws an error. It allows you to manage that error by changing your state, run effects or even return a new value to the next operators.
import { Operator, pipe, mutate, catchError } from 'overmind'
export const doSomething: Operator<string> = pipe(
mutate(() => {
throw new Error('foo')
}),
mutate(() => {
// This one is skipped
})
catchError(({ state }, error) => {
state.error = error.message
return 'value_to_be_passed_on'
}),
mutate(() => {
// This one continues executing with replaced value
})
)
When action is called multiple times within the set time limit, only the last action will move beyond the point of the debounce.
import { Operator, pipe, debounce } from 'overmind'
import * as o from './operators'
export const search: Operator<string> = pipe(
debounce(200),
o.performSearch()
)
Stop execution if it returns false.
import { Operator, filter } from 'overmind'
export const lengthGreaterThan: (length: number) => Operator<string> = (length) =>
filter(function lengthGreaterThan(_, value) {
return value.length > length
})
Allows you to pass each item of a value that is an array to the operator/pipe on the second argument.
import { Operator, pipe, forEach } from 'overmind'
import { Post } from './state'
import * as o from './operators'
export const openPosts: Operator<string, Post[]> = pipe(
o.getPosts(),
forEach(o.getAuthor())
)
Allows you to execute an operator/pipe based on the matching key.
{% tabs %} {% tab title="overmind/operators.ts" %}
import { fork, Operator } from 'overmind'
import { User } from './state'
export const forkUserType: (paths: { [key: string]: Operator<User> }) => Operator<User> = (paths) =>
fork(function forkUserType(_, user) {
return user.type
}, paths)
{% endtab %}
{% tab title="overmind/actions.ts" %}
import { Operator, pipe } from 'overmind'
import * as o from './operators'
import { UserType } from './state'
export const getUser: Operator<string, User> = pipe(
o.getUser(),
o.forkUserType({
[UserType.ADMIN]: o.doThis(),
[UserType.SUPERUSER]: o.doThat()
})
)
{% endtab %} {% endtabs %}
{% hint style="info" %} You have to use ENUM for these keys {% endhint %}
Returns a new value to the pipe. If the value is a promise it will wait until promise is resolved.
import { Operator, map } from 'overmind'
import { User } from './state'
export const getEventTargetValue: () => Operator<Event, string> = () =>
map(function getEventTargetValue(_, event) {
return event.currentTarget.value
})
async
You use this operator whenever you want to change the state of the app. Any returned value is ignored.
import { Operator, mutate } from 'overmind'
export const setUser: () => Operator<User> = () =>
mutate(function setUser({ state }, user) {
state.user = user
})
This operator does absolutely nothing. Is useful when paths of execution is not supposed to do anything.
import { Operator } from 'overmind'
import * as o from './operators'
export const doSomething: Operator = o.forkUserType({
superuser: o.doThis(),
admin: o.doThat(),
other: o.noop()
})
Will run every operator and wait for all of them to finish before moving on. Works like Promise.all.
import { Operator, parallel } from 'overmind'
import * as o from './operators'
export const loadAllData: Operator = parallel(
o.loadSomeData(),
o.loadSomeMoreData()
)
The pipe is an operator in itself. Use it to compose other operators and pipes.
import { Operator, pipe } from 'overmind'
import { Item } from './state'
import * as o from './operators'
export const openItem: Operator<string, Item> = pipe(
o.openItemsWhichIsAPipeOperator(),
o.getItem()
)
This operator allows you to run side effects. You can not change state and you can not return a value.
import { Operator, run } from 'overmind'
export const doSomething: <T>() => Operator<T> = () =>
run(function doSomething({ effects }) {
effects.someApi.doSomething()
})
This operator allows you to ensure that if an action is called, the next action will only continue past this point if a certain duration has passed. Typically used when an action is called many times in a short amount of time.
import { Operator, pipe, throttle } from 'overmind'
import * as o from './operators'
export const onMouseDrag: Operator<string> = pipe(
throttle(200),
o.handleMouseDrag()
)
This operator allows you to scope execution and manage errors. This operator does not return a new value to the execution.
import { Operator, pipe, tryCatch } from 'overmind'
import * as o from './operators'
export const doSomething: Operator<string> = tryCatch({
try: o.somethingThatMightError(),
catch: o.somethingToHandleTheError()
})
Hold execution for set time.
import { Operator, pipe, wait } from 'overmind'
import * as o from './operators'
export const search: Operator<string> = pipe(
wait(2000),
o.executeSomething()
)
Wait until a state condition is true.
import { Operator, pipe, waitUntil } from 'overmind'
import * as o from './operators'
export const search: Operator<string> = pipe(
waitUntil(state => state.count === 3),
o.executeSomething()
)
Go down the true or false path based on the returned value.
import { Operator, when } from 'overmind'
import { User } from './state'
export const whenUserIsAwesome: (paths: { true: Operator<User>, false: Operator<User> }) => Operator<User> = (paths) =>
when(function whenUserIsAwesome(_, user) {
return user.isAwesome
}, paths)