Published by zerobias over 4 years ago
effect.doneData
and effect.failData
events with effect result and error values as shorthands for common use cases effect.done.map(({result}) => result)
and effect.fail.map(({error}) => error)
import {createEffect, merge} from 'effector'
const fetchUserFx = createEffect()
const fetchFriendsFx = createEffect()
/* create event with results of any request to api */
/* new way, using .doneData events */
const apiResult = merge([
fetchUserFx.doneData,
fetchFriendsFx.doneData,
])
/* old way, using .done events */
const apiResultOld = merge([
fetchUserFx.done.map(({result}) => result),
fetchFriendsFx.done.map(({result}) => result),
])
Published by zerobias almost 5 years ago
effect.inFlight
store to effects. It show how many effect calls aren't settled yet. Useful for rate limiting.import {createEffect} from 'effector'
const fx = createEffect({
handler: () => new Promise(rs => setTimeout(rs, 500)),
})
fx.inFlight.watch(amount => {
console.log('in-flight requests:', amount)
})
// => 0
const req1 = fx()
// => 1
const req2 = fx()
// => 2
await Promise.all([req1, req2])
// => 1
// => 0
Documentation for effect.inFlight
withRegion
: region-based memory management tool, which attach units (stores, events and effects) and watchers, created inside given callback to lifecycle of owner unit to be erased together with it.import {createEvent, createDomain, withRegion} from 'effector'
const trigger = createEvent()
const domain = createDomain()
withRegion(domain, () => {
trigger.watch(n => {
console.log(n)
})
})
trigger(0)
// => 0
clearNode(domain)
trigger(1)
// no reaction
Map<Store, any>
to values
property in fork
.effect.pending
: it will become false
only after all pending effect calls becomes settled.Published by zerobias almost 5 years ago
launch({target: unit, params})
overload for launch
- low level method for running computation in units (events, effects or stores). Mostly used by library developers for fine-grained control of computations.Published by zerobias almost 5 years ago
effector/fork
and effector-react/ssr
: api for server side rendering and managing independent instances of application in general./**
* app
*/
import {createDomain, forward, restore} from 'effector'
import {useStore, useList, Provider, useEvent} from 'effector-react/ssr'
export const app = createDomain()
const requestUsername = app.createEffect<{login: string}, string>()
const requestFriends = app.createEffect<string, string[]>()
const username = restore(requestUsername, 'guest')
const friends = restore(requestFriends, [])
forward({
from: requestUserName.done.map(({result: username}) => username),
to: requestFriends,
})
const Friends = () => (
<ul>
{useList(friends, friend => (
<li>{name}</li>
))}
</ul>
)
const Title = () => <header>Hello {useStore(username)}</header>
export const View = ({root}) => (
<Provider value={root}>
<Title />
<Friends />
</Provider>
)
/**
* client
*/
import ReactDOM from 'react-dom'
import {fork} from 'effector/fork'
import {app, View} from './app'
const clientScope = fork(app, {
values: window.__initialState__,
})
ReactDOM.hydrate(<View root={clientScope} />, document.getElementById('root'))
/**
* server
*/
import express from 'express'
import {renderToString} from 'react-dom/server'
import {fork, serialize, allSettled} from 'effector/fork'
import {app, View} from './app'
export const server = express()
server.get('/user/:login', async (req, res) => {
// clone application
const scope = fork(app)
// call requestUsername(req.params) in scope
// and await all triggered effects
await allSettled(requestUsername, {
scope,
params: req.params, // argument for requestUsername call
})
// save all stores in application to plain object
const data = serialize(scope)
// render dom content
const content = renderToString(<View root={scope} />)
res.send(`
<body>
${content}
<script>
window.__initialState__ = ${JSON.stringify(data)};
</script>
</body>
`)
})
This solution requires effector/babel-plugin
in babel configuration:
{
"plugins": ["effector/babel-plugin"]
}
Example application with express
Serverless example
createApi
, stores created with restore
and events created with .prepend
to domain of given source unitsimport {createDomain, createApi, restore} from 'effector'
const domain = createDomain()
domain.onCreateStore(store => {
console.log('store created')
})
domain.onCreateEvent(event => {
console.log('event created')
})
const position = domain.createStore({x: 0})
// => store created
const {move} = createApi(position, {
move: ({x}, payload) => ({x: x + payload}),
})
// => event created
const lastMove = restore(move, 0)
// => store created
Published by zerobias almost 5 years ago
combine
batching in a few edge cases with nested combine
callsimport {createEvent, createStore, combine} from 'effector'
const event = createEvent()
const store = createStore(0).on(event, s => s + 1)
const combined = combine([store, combine([store.map(d => d + 1)])])
combined.watch(e => fn(e))
// => [0, [1]]
event()
// => [1, [2]]
Published by zerobias almost 5 years ago
fn
argument types without as const
in useStoreMap
.useStoreMap
types, which helps to infer types of fn
arguments from keys
. And now useStoreMap
types improved even more: every item in second argument will have its own type even without as const
, out from a boxPR #274 (thanks @abliarsar)
import React from 'react'
import {createStore} from 'effector'
import {useStoreMap} from 'effector-react'
type User = {
username: string
email: string
bio: string
}
const users = createStore<User[]>([
{
username: 'alice',
email: '[email protected]',
bio: '. . .',
},
{
username: 'bob',
email: '[email protected]',
bio: '~/ - /~',
},
{
username: 'carol',
email: '[email protected]',
bio: '- - -',
},
])
export const UserProperty = ({id, field}: {id: number; field: keyof User}) => {
const value = useStoreMap({
store: users,
keys: [id, field],
fn: (users, [id, field]) => users[id][field] || null,
})
return <div>{value}</div>
}
Published by zerobias almost 5 years ago
import {createStore, createEvent, sample, combine} from 'effector'
const trigger = createEvent()
const objectTarget = createEvent()
const arrayTarget = createEvent()
const a = createStore('A')
const b = createStore('B')
sample({
source: {a, b},
clock: trigger,
target: objectTarget,
})
sample({
source: [a, b],
clock: trigger,
target: arrayTarget,
})
objectTarget.watch(obj => {
console.log('sampled object', obj)
})
arrayTarget.watch(array => {
console.log('sampled array', array)
})
trigger()
// sampled object {a: 'A', b: 'B'}
// sampled array ['A', 'B']
/* old way to do this: */
sample({
source: combine({a, b}),
clock: trigger,
target: objectTarget,
})
sample({
source: combine([a, b]),
clock: trigger,
target: arrayTarget,
})
Published by zerobias almost 5 years ago
Gate.open
& Gate.close
eventsimport {createGate} from 'effector-react'
const PageMeta = createGate()
PageMeta.open.watch(props => {
console.log('page meta', props)
})
const App = () => (
<>
<PageMeta name="admin page" />
<div>body</div>
</>
)
ReactDOM.render(<App />, document.getElementById('root'))
// => page meta {name: 'admin page'}
Published by zerobias almost 5 years ago
domain.createStore
as alias for domain.store
(proposal)domain.createEvent
as alias for domain.event
domain.createEffect
as alias for domain.effect
domain.createDomain
as alias for domain.domain
Published by zerobias almost 5 years ago
sample
typings for typescript (PR #248, fix #247) (thanks @bloadvenro)Published by zerobias almost 5 years ago
Published by zerobias almost 5 years ago
forward
import {createEvent, forward} from 'effector'
const firstSource = createEvent()
const secondSource = createEvent()
const firstTarget = createEvent()
const secondTarget = createEvent()
forward({
from: [firstSource, secondSource],
to: [firstTarget, secondTarget]
})
firstTarget.watch(e => console.log('first target', e))
secondTarget.watch(e => console.log('second target', e))
firstSource('A')
// => first target A
// => second target A
secondSource('B')
// => first target B
// => second target B
Published by zerobias almost 5 years ago
createComponent
HOC for TypeScript usage. This HOC provides type-safe properties in vue components.// component.vue
import {createStore, createApi} from 'effector'
import {createComponent} from 'effector-vue'
const $counter = createStore(0)
const {update} = createApi($counter, {
update: (_, value: number) => value,
})
export default createComponent(
{
name: 'Counter',
methods: {
update,
handleClick() {
const value = this.$counter + 1 // this.$counter <- number ( typescript tips )
this.update(value)
},
},
},
{$counter},
)
Published by zerobias almost 5 years ago
createStoreObject
to combine
to reduce api surface. Wherever createStoreObject
was used, it can be replaced with combine
import {createStore, combine, createStoreObject} from 'effector'
const r = createStore(255)
const g = createStore(0)
const b = createStore(255)
const color = combine({r, g, b})
color.watch(console.log)
// => {r: 255, b: 0, b: 255}
const colorOld = createStoreObject({r, g, b})
colorOld.watch(console.log)
// => {r: 255, b: 0, b: 255}
combine
import {createStore, combine} from 'effector'
const r = createStore(255)
const g = createStore(0)
const b = createStore(255)
const color = combine([r, g, b])
color.watch(console.log)
// => [255, 0, 255]
Published by zerobias almost 5 years ago
effect.done
and effect.fail
are called before effect.finally
watchers, thereby preventing side-effects from interrupting pure computationsPublished by zerobias almost 5 years ago
sample({clock: undefined})
import {createStore, sample} from 'effector'
sample({
source: createStore(null),
clock: undefined,
})
// Throw "config.clock should be defined"
Published by zerobias about 5 years ago
forward
typings for typescript (PR #229, fix #174) (thanks @bloadvenro)clearNode(domain)
, introduced in effector 20.2.0
Published by zerobias about 5 years ago
const counter = createStore(0)
new Vue({
effector: {
counter, // would create `counter` in template
},
})
Published by zerobias about 5 years ago
guard
: conditional event routingimport {createStore, createEffect, createEvent, guard, sample} from 'effector'
const clickRequest = createEvent()
const fetchRequest = createEffect({
handler: n => new Promise(rs => setTimeout(rs, 2500, n)),
})
const clicks = createStore(0).on(clickRequest, x => x + 1)
const requests = createStore(0).on(fetchRequest, x => x + 1)
const isIdle = fetchRequest.pending.map(pending => !pending)
/*
on clickRequest, take current clicks value,
and call fetchRequest with it
if isIdle value is true
*/
guard({
source: sample(clicks, clickRequest),
filter: isIdle,
target: fetchRequest,
})
See ui visualization
Also, guard
can accept common function predicate as a filter, to drop events before forwarding them to target
import {createEffect, createEvent, guard} from 'effector'
const searchUser = createEffect()
const submitForm = createEvent()
guard({
source: submitForm,
filter: user => user.length > 0,
target: searchUser,
})
submitForm('') // nothing happens
submitForm('alice') // ~> searchUser('alice')
Published by zerobias about 5 years ago
split
method (PR)import React from 'react'
import ReactDOM from 'react-dom'
import {createStore, createEvent, createEffect, sample} from 'effector'
import {useList} from 'effector-react'
const items$ = createStore([{id: 0, status: 'NEW'}, {id: 1, status: 'NEW'}])
const updateItem = createEvent()
const resetItems = createEvent()
const processItems = createEffect({
async handler(items) {
for (let {id} of items) {
//event call inside effect
//should be applied to items$
//only after processItems itself
updateItem({id, status: 'PROCESS'})
await new Promise(r => setTimeout(r, 3000))
updateItem({id, status: 'DONE'})
}
},
})
items$
.on(updateItem, (items, {id, status}) =>
items.map(item => (item.id === id ? {...item, status} : item)),
)
.on(processItems, items => items.map(({id}) => ({id, status: 'WAIT'})))
.reset(resetItems)
const processClicked = createEvent()
sample({
source: items$,
clock: processClicked,
target: processItems,
})
const App = () => (
<section>
<header>
<h1>Jobs list</h1>
</header>
<button onClick={processClicked}>run tasks</button>
<button onClick={resetItems}>reset</button>
<ol>
{useList(items$, ({status}) => (
<li>{status}</li>
))}
</ol>
</section>
)
ReactDOM.render(<App />, document.getElementById('root'))