Changelog
See also separate changelogs for each library
effector-vue 20.4.0
import {createStore} from 'effector'
import {createComponent} from 'effector-vue'
const counter = createStore(0)
const component = createComponent(
{
template: '<div>{{ counter }}</div>',
watch: {
counter() {
/* side-effects here */
},
},
},
{counter},
)
effector-react 20.6.2, effector-vue 20.3.3
- Fix umd build of effector-react and effector-vue
Cdn with umd build of effector-react Cdn with umd build of effector-vue
effector 20.12.1
- Add support for guard to babel-plugin
- Add support for forward to babel-plugin
- Add support for explicit
domain.hooks
calls as escape hatch for imperative adding units to given domain
effector 20.12.0
- Add
effect.doneData
andeffect.failData
events with effect result and error values as shorthands for common use caseseffect.done.map(({result}) => result)
andeffect.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),
])
Documentation for effect.doneData
Documentation for effect.failData
effector 20.11.0
- Add
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
- Introduce
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
- Add support for
Map<Store, any>
tovalues
property infork
. - Fix concurrent requests support in
effect.pending
: it will becomefalse
only after all pending effect calls becomes settled.
effector 20.10.0
- Add
launch({target: unit, params})
overload forlaunch
- low level method for running computation in units (events, effects or stores). Mostly used by library developers for fine-grained control of computations.
effector 20.9.0, effector-react 20.6.0
- Introduce
effector/fork
andeffector-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'
// initialize app with values from server
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
- Add events created with
createApi
, stores created withrestore
and events created with.prepend
to domain of given source units
import {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
effector 20.8.2
- Improve
combine
batching in a few edge cases with nestedcombine
calls
import {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]]
effector-react 20.5.2
- Add ability to infer
fn
argument types withoutas const
inuseStoreMap
. In effector-react 20.0.3 we introduced an improvement foruseStoreMap
types, which helps to infer types offn
arguments fromkeys
. And nowuseStoreMap
types improved even more: every item in second argument will have its own type even withoutas const
, out from a box
PR #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: 'alice@example.com',
bio: '. . .',
},
{
username: 'bob',
email: 'bob@example.com',
bio: '~/ - /~',
},
{
username: 'carol',
email: 'carol@example.com',
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>
}
effector 20.8.0
- Allow to use objects and arrays with stores in sample source
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,
})
effector-react 20.5.0
- Pass props to
Gate.open
&Gate.close
events
import {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'}
effector 20.7.0
- Add
domain.createStore
as alias fordomain.store
(proposal) - Add
domain.createEvent
as alias fordomain.event
- Add
domain.createEffect
as alias fordomain.effect
- Add
domain.createDomain
as alias fordomain.domain
effector 20.6.2
- Improve
sample
typings for typescript (PR #248, fix #247) (thanks @bloadvenro)
effector 20.6.1, effector-react 20.4.1, effector-vue 20.3.2
- Add typescript typings for compat builds
- Improve built-in source maps
effector 20.6.0
- Add support for arrays to
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
effector-vue 20.3.0
- Add
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},
)
effector 20.5.0
- Merge
createStoreObject
tocombine
to reduce api surface. WherevercreateStoreObject
was used, it can be replaced withcombine
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}
- Add ability to use arrays of stores with
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]
effector 20.4.4
- Ensure that both
effect.done
andeffect.fail
are called beforeeffect.finally
watchers, thereby preventing side-effects from interrupting pure computations
effector 20.4.3
- Throw expected error in case with
sample({clock: undefined})
import {createStore, sample} from 'effector'
sample({
source: createStore(null),
clock: undefined,
})
// Throw "config.clock should be defined"
effector 20.4.1
- Improve
forward
typings for typescript (PR #229, fix #174) (thanks @bloadvenro) - Add typescript typings for
clearNode(domain)
, introduced in effector 20.2.0
effector-vue 20.2.1
- Add typescript typings for object shape, introduced in effector-vue 20.2.0
const counter = createStore(0)
new Vue({
effector: {
counter, // would create `counter` in template
},
})
effector 20.4.0
- Introduce
guard
: conditional event routing Control one event with the help of another: when the condition and the data are in different places, then we can use guard with stores as a filters to trigger events when condition state is true, thereby modulate signals without mixing them
import {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')
Type inference Implementation tests
- Introduce
name
property insample
parameters list
Each basic entity in Effector (event/effect/store/domain) may have a name. You now can name sampled entities in the same manner as basic ones.
import {createStore, sample} from 'effector'
const foo = createStore(null)
const sampled = sample({
source: foo,
name: 'sampled foo',
})
console.log(sampled.shortName) // 'sampled foo'
effector 20.3.2
- Allow typescript to refine type with
split
method (PR) - Improve type inference of effects with optional arguments in Typescript (PR)
- Ensure that effect handler is called only after effect update itself, thereby preventing side-effects from interrupting pure computations
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'))
effector 20.3.1
- Fix edge case when
clearNode
been called on store belonged to certain domain led to the removal of the entire domain
effector-react 20.4.0
- Add support for
keys
field inuseList
. By default,useList
rerenders only when some of its items was changed. Howewer, sometimes we need to update items when some external value (e.g. props field or state of another store) is changed. In such cases we need to tell react about our dependencies and pass keys explicitly.
import React from 'react'
import ReactDOM from 'react-dom'
import {createEvent, createStore, restore} from 'effector'
import {useStore, useList} from 'effector-react'
const renameUser = createEvent()
const user = restore(renameUser, 'alice')
const friends = createStore(['bob'])
const List = () => {
const userName = useStore(user)
return useList(friends, {
keys: [userName],
fn: friend => (
<div>
{friend} is a friend of {userName}
</div>
),
})
}
ReactDOM.render(<List />, document.getElementById('root'))
// => <div> bob is a friend of alice </div>
setTimeout(() => {
renameUser('carol')
// => <div> bob is a friend of carol </div>
}, 500)
effector 20.3.0
- Add
shortName
to domains
import {createDomain} from 'effector'
const domain = createDomain('feature')
console.log(domain.shortName)
// => feature
- Add
history
to domains with read-only sets of events, effects, stores and subdomains
import {createDomain} from 'effector'
const domain = createDomain()
const eventA = domain.event()
const storeB = domain.store(0)
console.log(domain.history)
// => {stores: Set{storeB}, events: Set{eventA}, domains: Set, effects: Set}
effector-vue 20.2.0
- Add support for object shape
const counter = createStore(0)
new Vue({
effector: {
counter, // would create `counter` in template
},
})
effector 20.2.0
- Add support for domains to
clearNode
import {createDomain, clearNode} from 'effector'
const root = createDomain()
const child = root.domain()
clearNode(child)
- Add
.sid
- stable hash identifier for events, effects, stores and domains, preserved between environments, to handle client-server interaction within the same codebase.
/* common.js */
import {createEffect} from 'effector'
export const getUser = createEffect({sid: 'GET /user'})
console.log(getUsers.sid)
// => GET /user
/* worker.js */
import {getUsers} from './common'
getUsers.use(userID => fetch(userID))
getUsers.done.watch(({result}) => {
postMessage({sid: getUsers.sid, result})
})
onmessage = async ({data}) => {
if (data.sid !== getUsers.sid) return
getUsers(data.userID)
}
/* client.js */
import {createEvent} from 'effector'
import {getUsers} from './common'
const onMessage = createEvent()
const worker = new Worker('worker.js')
worker.onmessage = onMessage
getUsers.use(
userID =>
new Promise(rs => {
worker.postMessage({sid: getUsers.sid, userID})
const unwatch = onMessage.watch(({data}) => {
if (data.sid !== getUsers.sid) return
unwatch()
rs(data.result)
})
}),
)
The key is that sid can be autogenerated by effector/babel-plugin
with default config and it will be stable between builds
See example project
- Add support for implicit void params in
createEffect
for typescript #106
const handler = () => console.log()
const effect = createEffect({handler})
effect()
- Fix bug with
cannot read property .toString of undefined
error during store initialization
effector-react 20.3.0
- Add support for react hooks in
createComponent
effector-react 20.2.2, effector-vue 20.1.2
effector-react
,effector-vue
andeffector
itself have compat builds for compatibility with old devices without babel. In such versions, it should importeffector/compat
, not justeffector
(Fix #173)
effector 20.1.2
- Allow typescript to refine type of payload if
event.filter({fn})
got a predicate function as a callback PR
import {createEvent} from 'effector'
const event = createEvent<string | number>()
const isString = (value: any): value is string => typeof value === 'string'
const isNumber = (value: any): value is number => typeof value === 'number'
const str = event.filter({fn: isString}) // Event<string>
const num = event.filter({fn: isNumber}) // Event<number>
str.watch(value => value.slice()) // OK now
num.watch(value => value.toFixed(2)) // OK now
- Allow typescript to refine type with
is
methods PR
import {is} from 'effector'
//result has type Event<any> | void
function getEvent(obj: unknown) {
if (is.event(obj)) return obj
if (is.store(obj)) return obj.updates
}
- Add new fields to definition of graph nodes (discussion)
effector 20.1.1
- Add support for IE11 to
effector/compat
- Fix flow typings for
sample
- Allow
effector/babel-plugin
to work in browser
effector-react 20.2.1, effector-vue 20.1.1
- Add support for IE11 to
effector-react/compat
andeffector-vue/compat
effector 20.1.0
- Add
effector/compat
module to use with Smart TV (Chrome 47) apps without babel (fix #152). Starting with this release, the library code is tested by browserstack.com for compatibility with our targets, including smart tv - Improve typescript typings for
sample
(thanks @abliarsar) (PR #156) - Fix webpack issue, which generated incorrect code with some ancient targets (IE10)
effector-react 20.2.0
- Add
effector-react/compat
module to use with Smart TV (Chrome 47) apps without babel
effector-vue 20.1.0
- Add
effector-vue/compat
module to use with Smart TV (Chrome 47) apps without babel
effector-react 20.1.1
- Add
useList
for efficient rendering of store lists
import React from 'react'
import ReactDOM from 'react-dom'
import {createStore} from 'effector'
import {useList} from 'effector-react'
const list = createStore([
{name: 'alice', age: 21},
{name: 'bob', age: 20},
{name: 'carol', age: 22},
])
const List = () => {
// note that we don't need keys here any more
const users = useList(list, ({name}, i) => (
<div>
{i}) {name}
</div>
))
return <div>{users}</div>
}
ReactDOM.render(<List />, document.getElementById('root'))
effector-react 20.0.5
- Fix irrelevant react memory leak warning in a few cases
effector-react 20.0.4
- Fix a bug in
useStore
with lack of store updates triggered by react hooks in children components
effector-react 20.0.3
- Allow
as const
typescript assertion foruseStoreMap
keys. It helps us to infer type forfn
arguments
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: 'alice@example.com',
bio: '. . .',
},
{
username: 'bob',
email: 'bob@example.com',
bio: '~/ - /~',
},
{
username: 'carol',
email: 'carol@example.com',
bio: '- - -',
},
])
export const UserProperty = ({id, field}: {id: number; field: keyof User}) => {
const value = useStoreMap({
store: users,
keys: [id, field] as const,
fn: (users, [id, field]) => users[id][field] || null,
})
return <div>{value}</div>
}
In typescript versions below 3.4, you can still use an explicit type assertion
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: 'alice@example.com',
bio: '. . .',
},
{
username: 'bob',
email: 'bob@example.com',
bio: '~/ - /~',
},
{
username: 'carol',
email: 'carol@example.com',
bio: '- - -',
},
])
export const UserProperty = ({id, field}: {id: number; field: keyof User}) => {
const value = useStoreMap({
store: users,
keys: [id, field] as [number, keyof User],
fn: (users, [id, field]) => users[id][field] || null,
})
return <div>{value}</div>
}
as const in typescript docs
effector-react 20.0.2
- Fix bug with additional rerender in case of
useStore
argument change
effector-react 20.0.1
- Fix flow typings for
useStoreMap
effector 20.0.0
- Add
merge
for merging events
import {createEvent, merge} from 'effector'
const foo = createEvent()
const bar = createEvent()
const baz = merge([foo, bar])
baz.watch(v => console.log('merged event triggered: ', v))
foo(1)
// => merged event triggered: 1
bar(2)
// => merged event triggered: 2
- Add
split
for pattern-matching over events
import {createEvent, split} from 'effector'
const message = createEvent()
const messageByAuthor = split(message, {
bob: ({user}) => user === 'bob',
alice: ({user}) => user === 'alice',
})
messageByAuthor.bob.watch(({text}) => {
console.log('[bob]: ', text)
})
messageByAuthor.alice.watch(({text}) => {
console.log('[alice]: ', text)
})
message({user: 'bob', text: 'Hello'})
// => [bob]: Hello
message({user: 'alice', text: 'Hi bob'})
// => [alice]: Hi bob
/* default case, triggered if no one condition met */
const {__: guest} = messageByAuthor
guest.watch(({text}) => {
console.log('[guest]: ', text)
})
message({user: 'unregistered', text: 'hi'})
// => [guest]: hi
- Allow
clearNode
to automatically dispose all related intermediate steps
import {createEvent, clearNode} from 'effector'
const source = createEvent()
const target = source.map(x => {
console.log('intermediate step')
return x
})
target.watch(x => console.log('target watcher'))
source()
// => intermediate step
// => target watcher
clearNode(target)
source() // ~ no reaction ~
Fix promise warning for effects
Add
effect.finally
import {createEffect} from 'effector'
const fetchApi = createEffect({
handler: n =>
new Promise(resolve => {
setTimeout(resolve, n, `${n} ms`)
}),
})
fetchApi.finally.watch(response => {
console.log(response)
})
await fetchApi(10)
// => {status: 'done', result: '10 ms', params: 10}
// or
// => {status: 'fail', error: Error, params: 10}
- Add types for createEvent with config instead of string
- Add types for createEffect with config instead of string
- Add
event.filterMap
as new alias forevent.filter(fn)
- Remove
extract
,withProps
,is.*
re-exports
effector-react 20.0.0
- Removed unstable_createStoreProvider
effector-vue 20.0.0
Vue adapter for effector 20
effector-react 19.1.2
- Add
useStoreMap
hook to select part from a store. Motivation
import {createStore} from 'effector'
import {useStore, useStoreMap} from 'effector-react'
import React from 'react'
import ReactDOM from 'react-dom'
const User = ({id}) => {
const user = useStoreMap({
store: user$,
keys: [id],
fn: (users, [id]) => users[id],
})
return (
<div>
{user.name} ({user.age})
</div>
)
}
const UserList = () => useStore(userID$).map(id => <User id={id} key={id} />)
const user$ = createStore({
alex: {age: 20, name: 'Alex'},
john: {age: 30, name: 'John'},
})
const userID$ = user$.map(users => Object.keys(users))
ReactDOM.render(<UserList />, document.getElementById('root'))
effector 19.1.0
- Add support for
event.filter
with common predicate functions
import {createEvent} from 'effector'
const event = createEvent()
// that event will be triggered only when fn returns true
const filtered = event.filter({
fn: x => x > 0,
})
filtered.watch(x => console.log('x =', x))
event(-2) // nothing happens
event(2) // => x = 2
effector-vue 19.0.1
- Fix typescript typings #116
effector, effector-react, effector-vue 19.0.0
To indicate the stability of the project, we adopting semantic versioning and happy to announce version 19.0.0 for all packages. And to make the transition easier, that release contains no breaking changes; simple replacement of “^0.18.*” to “^19.0.0” is safe for sure ☄️
effector 0.18.10-0.18.11
- Implement event
store.updates
, representing updates of given store. Use case: watchers, which will not trigger immediately after creation (unlikestore.watch
)
import {createStore, is} from 'effector'
const clicksAmount = createStore(0)
is.event(clicksAmount.updates) // => true
clicksAmount.watch(amount => {
console.log('will be triggered with current state, immediately, sync', amount)
})
clicksAmount.updates.watch(amount => {
console.log('will not be triggered unless store value is changed', amount)
})
effector 0.18.9
- Allow
clearNode
to erase information from the node itself, in addition to the existing opportunity to erase subscribers (thanks @artalar)
effector 0.18.7-0.18.8
- Add support for passing multiply items at once in
store.reset
import {createStore, createEvent} from 'effector'
const firstTrigger = createEvent()
const secondTrigger = createEvent()
const target = createStore(0).reset(firstTrigger, secondTrigger)
Add support for
createEvent
andcreateEffect
with config (see next code example)Add
.pending
property for effects
import {createEffect} from 'effector'
import {createComponent} from 'effector-react'
import React from 'react'
const fetchApi = createEffect({
handler: n => new Promise(resolve => setTimeout(resolve, n)),
})
fetchApi.pending.watch(console.log)
const Loading = createComponent(
fetchApi.pending,
(props, pending) => pending && <div>Loading...</div>,
)
fetchApi(5000)
it’s a shorthand for common use case
import {createEffect, createStore} from 'effector'
const fetchApi = createEffect()
//now you can use fetchApi.pending instead
const isLoading = createStore(false)
.on(fetchApi, () => true)
.on(fetchApi.done, () => false)
.on(fetchApi.fail, () => false)
- Introduce
sample
. Sample allows to integrate rapidly changed values with common ui states
import {createStore, createEvent, sample} from 'effector'
import {createComponent} from 'effector-react'
import React from 'react'
const tickEvent = createEvent()
const tick = createStore(0).on(tickEvent, n => n + 1)
setInterval(tickEvent, 1000 / 60)
const mouseClick = createEvent()
const clicks = createStore(0).on(mouseClick, n => n + 1)
const sampled = sample(tick, clicks, (tick, clicks) => ({
tick,
clicks,
}))
const Monitor = createComponent(sampled, (props, {tick, clicks}) => (
<p>
<b>tick: </b>
{tick}
<br />
<b>clicks: </b>
{clicks}
</p>
))
const App = () => (
<div>
<Monitor />
<button onClick={mouseClick}>click to update</button>
</div>
)
- Add babel plugin for automatic naming of events, effects and stores (useful for identifying resources with SSR)
- Add babel plugin for automatic displayName for react components
import {createStore, createEvent} from 'effector'
import {createComponent} from 'effector-react'
import React from 'react'
const title = createStore('welcome')
console.log('store.shortName', title.shortName)
// store.shortName title
const clickTitle = createEvent()
console.log('event.shortName', clickTitle.shortName)
// store.shortName clickTitle
const Title = createComponent(title, (props, title) => <h1>{title}</h1>)
console.log('Component.displayName', Title.displayName)
// Component.displayName Title
Plugins are available out from a box
.babelrc
:
{
"plugins": ["effector/babel-plugin", "effector/babel-plugin-react"]
}
- Add support for passing events and effects to watchers
import {createStore, createEvent} from 'effector'
const updates = createEvent()
const state = createStore(0)
state.watch(updates)
- Improve execution order for sync effects
- Improve typescript typings for createApi (#102)
effector-react 0.18.10
- Add initial props factory to
createComponent
import {users} from './feature'
const UserItem = createComponent(
initialProps => users.map(users => users[initialProps.id]),
(_, user) => {
return <div>{user.username}</div>
},
)
const UserList = createComponent(users, (_, users) => {
return users.map(user => <TodoItem key={user.id} id={user.id} />)
})
- Implicitly convert objects to
createStoreObject
increateComponent
const deposit = createEvent()
const username = createStore('zerobias')
const balance = createStore(0)
const Profile = createComponent(
{username, balance},
(_, {username, balance}) => {
return (
<div>
Hello, {username}. Your balance is {balance}
<button onClick={deposit}>Deposit</button>
</div>
)
},
)
- Add
mounted
andunmounted
events to components created bycreateComponent
import {counter, increment} from './feature'
const Counter = createComponent(counter, (_, state) => {
return (
<div>
{state}
<button onClick={increment}>+</button>
</div>
)
})
Counter.mounted.watch(({props, state}) => {
counter.on(increment, s => s + 1)
})
Counter.unmounted.watch(({props, state}) => {
counter.off(increment)
})
- Replace
useLayoutEffect
withuseIsomorphicLayoutEffect
to support server-side rendering
effector-react 0.18.9
- Replace
useEffect
withuseLayoutEffect
inuseStore
hook to response to state changes immediately
0.18.5-0.18.6
- Optimize combined stores: no intermediate steps no more
import {createStore, createEvent, createStoreObject, combine} from 'effector'
const field = createStore('')
const isEmpty = field.map(value => value.length === 0)
const isTooLong = field.map(value => value.length > 12)
const isValid = combine(
isEmpty,
isTooLong,
(isEmpty, isTooLong) => !isEmpty && !isTooLong,
)
const updateField = createEvent('update field value')
field.on(updateField, (state, upd) => upd.trim())
createStoreObject({
field,
isEmpty,
isTooLong,
isValid,
}).watch(data => {
console.log(data)
})
// => {field: '', isEmpty: true, isTooLong: false, isValid: false}
updateField('bobby')
// => {field: 'bobby', isEmpty: false, isTooLong: false, isValid: true}
Use the new kernel. Provide improved eventual consistency: any side effects will be triggered only after performing all pure computations
Add
is
namespace for all type validators
import {createStore, createEvent, is} from 'effector'
const store = createStore('value')
const event = createEvent('some event')
is.store(store) // => true
is.event(store) // => false
is.unit(store) // => true
is.store(event) // => false
is.event(event) // => true
is.unit(event) // => true
is.store(null) // => false
is.event(null) // => false
is.unit(null) // => false
Add
clearNode
to break references and subscriptions between events, stores, etcAdd support for custom datatypes by making
step
constructors,createNode
andlaunch
functions public
import {createNode, launch, step, createStore} from 'effector'
const target = createStore(0)
target.watch(n => console.log('current n = ', n))
// => current n = 0
const customNode = createNode({
scope: {max: 100, lastValue: -1, add: 10},
child: [target], // you can forward later as well
node: [
step.compute({
fn: (arg, {add}) => arg + add,
}),
step.filter({
fn: (arg, {max, lastValue}) => arg < max && arg !== lastValue,
}),
step.compute({
fn(arg, scope) {
scope.lastValue = arg
return arg
},
}),
],
})
launch(customNode, 3)
// => current n = 13
launch(customNode, 95)
// no reaction, as 95 + 10 > 100
launch(customNode, 5)
// => current n = 15
launch(customNode, 5)
// no reaction, as we filtered it out with step.filter
- Fix
fromObservable
, ensure it works withredux
as a typical library withSymbol.observable
support
import {fromObservable} from 'effector'
import * as redux from 'redux'
const INCREMENT_STATE = 'INCREMENT_STATE'
const reduxStore = redux.createStore((state = 1, action) => {
switch (action.type) {
case INCREMENT_STATE:
return state + 1
default:
return state
}
})
const updateEvent = fromObservable(reduxStore)
updateEvent.watch(state => {
console.log('redux state = ', state)
})
reduxStore.dispatch({type: INCREMENT_STATE})
// => redux state = 1
- Fix
version
, now it always equals version in package.json
import {version} from 'effector'
console.log(version)
// => 0.18.6
- Add support forwarding to effects
import {createEffect, createEvent, forward} from 'effector'
const trigger = createEvent()
const sideEffect = createEffect('side-effect', {
async handler(args) {
await new Promise(rs => setTimeout(rs, 500))
console.log('args: ', args)
},
})
forward({
from: trigger,
to: sideEffect,
})
trigger('payload')
// ~ after 500 ms
// => args: payload
0.18.3-0.18.4
- Add version variable to public exports
import {version} from 'effector'
console.log(version)
Add effect handler to domain 4c6ae8
Add
Unit<T>
as common interface implemented byEvent
,Effect
andStore
Add
isStore
,isEvent
,isEffect
andisUnit
validators
import {createStore, createEvent, isStore, isEvent, isUnit} from 'effector'
const store = createStore('value')
const event = createEvent('some event')
isStore(store) // => true
isEvent(store) // => false
isUnit(store) // => true
isStore(event) // => false
isEvent(event) // => true
isUnit(event) // => true
isStore(null) // => false
isEvent(null) // => false
isUnit(null) // => false
- Add extended
createStore
with config
import {createStore} from 'effector'
const store = createStore('value', {
name: 'value store',
})
Publish babel-plugins
Improve naming for chrome performance timeline
Fix typescript typings #45
Fix
event.prepend
bug #35
0.18.2
Fix webpack usage issue. To prevent this in a future, webpack integration test was added.
Improve typescript typings for
createApi
. This code example became type checked
import {createStore, createApi} from 'effector'
const text = createStore('')
const {addMessage, cut} = createApi(text, {
addMessage: (text, message) => text + `\n` + message
cut: (text, {fromIndex, size}) => text.slice(fromIndex, fromIndex + size),
})
- Add umd bundle to npm. Therefore, you can use cdn to include library without bundlers
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/effector@0.18.2/effector.umd.js"></script>
</head>
<body>
<script>
const header = document.createElement('h1')
document.body.appendChild(header)
const text = effector.createStore('hello')
text.watch(str => (header.innerHTML = str))
</script>
</body>
</html>
0.18.1
- Add
forward
: common function for forwarding updates and events
import {forward} from 'effector'
const unsubscribe = forward({
from: Event | Store,
to: Event | Store | Effect,
})
- add support for storages in
store.on
import {createStore} from 'effector'
const name = createStore('name')
const counter = createStore(0).on(name, (count, name) => count++)
- Allow to pass
{handler: Function}
as second argument tocreateEffect
import {createEffect} from 'effector'
const callApi = createEffect('call api', {
async handler(url) {
const res = await fetch(url)
return res
},
})
- Make
effect.use
return the same effect instead of void (ability to chain method calls)
import {createEffect} from 'effector'
const callApi = createEffect('call api').use(url => fetch(url))
0.18.0
- Log events into Chrome devtools performance timeline
- Add notifications about errors inside computation chain
- Add
store.defaultState
property - effector-react: Add
createComponent
- Make
withProps
static function - Make effect return plain promise
0.18.0-beta.10
- Add Gate
import {type Gate, createGate} from 'effector-react'
const AppGate = createGate('app')
const MainPageGate = AppGate.childGate('main page')
export default ({isLoading, meta}) => (
<div>
Application
<AppGate isLoading={isLoading} />
{!isLoading && (
<div>
Main page
<MainPageGate meta={meta} />
</div>
)}
</div>
)
AppGate.state.watch(({isLoading}) => isLoading)
0.17.7
- Keep and replay the whole domain history for every new hook
0.17.6
- Add domain hooks for handle new events, effects or stores in domain.
import {createDomain} from 'effector'
const mainPage = createDomain('main page')
mainPage.onCreateEvent(event => {
console.log('new event: ', event.getType())
})
mainPage.onCreateStore(store => {
console.log('new store: ', store.getState())
})
const mount = mainPage.event('mount')
// => new event: main page/mount
const pageStore = mainPage.store(0)
// => new store: 0
- Improve TypeScript typings
0.17.5
- Add ability to use createEvent, createEffect and createDomain without arguments (omit name)
- Fix wrong order of effect names
- Add
createWrappedDomain
to watch all nested events and updates - Add
extract
to watch only part of nested storages - Deprecate
.epic
method (library supports symbol-observable, so assumed thatmost.from(event)
orObservable.Of(store)
covered all use cases)
0.17.4
- effector-react: Add check for mounting of store consumer
- Add
effect.use.getCurrent()
method to get current used function - Improve type inference in flow typing for
createStoreObject
- Improve public ts and flow typings
0.17.3
- Fix effector-react typings
- Build with node 6 target, add engine field to package.json
- Add warning dependency
0.17.2
- Memoize store.map and store updates
0.17.0
- Added sync graph reduction engine (it’s internal)
- Added store updates memoization
- Introduced effector-react
0.16.0
- Removed most-subject dependency
- New api
0.15.0-rc.2
- Add AVar: low-level interface for asynchronous variables
- Clean up builds before publishing
- Add types dir into npm build
0.14.0
- Add independent
createStore
method - Replace console.warn with console.error in warnings
- Make reducers full-featured store elements (add
.get()
,.set(x)
and.map(fn)
methods) - Add observable declaration to effects, events and reducers, which allow interop in this way:
from(effect)
0.13.0
- Build via rollup
- New module architechture
0.12.0
- Exclude coverage from npm build
- Rename
mill
tocollect
- Rename
joint
tocombine
0.11.1
- Remove source files from npm release
0.11.0
- Add support for sync functions in
.use
- breaking Rename config option
effectImplementationCheck
tounused
0.10.2
- Fix overriding of flow modules
0.10.0
- breaking Removed
rootDomain
alias forcreateRootDomain
- Fixed duplication of
typeConstant
events - Added sync event propagation
- Catching of watch function errors
- Added warning to port errors
- Added type aliases
DomainAuto
,EventAuto
andEffectAuto
- Added
mill
fluent “AND” reducer combinator
import {mill, type MillType, type Reducer} from 'effector'
type A = 'foo'
type B = 'bar'
declare var reducerA: Reducer<A>
declare var reducerB: Reducer<B>
const tuple: MillType<A, B> = mill()
.and(reducerA)
.and(reducerB)
const union: Reducer<{
a: A,
b: B,
staticField: string,
}> = tuple.joint((a: A, b: B) => ({
a,
b,
staticField: 'its ok',
}))
0.9.1
- Added hot reload support for root domains
- Added support for dispatching halt action
import {createHaltAction} from 'effector'
store.dispatch(createHaltAction()) //This store will be unsubscribed
0.9.0
First stable version
Before 0.9.0
Proof of concept