Simple React state management. Made with ❤️ and ES6 Proxies.
Table of Contents
Easy State consists of two wrapper functions only. store
creates state stores and view
creates reactive components, which re-render when state stores are mutated. The rest is just plain JavaScript, that you already know.
import React from 'react'
import { store, view } from 'react-easy-state'
// stores are normal objects
const clock = store({ time: new Date() })
setInterval(() => clock.time = new Date(), 1000)
// reactive components re-render on store mutations
function ClockComp () {
return <div>The time is: {clock.time}</div>
}
export default view(ClockComp)
npm install react-easy-state
Setting up a quick project
Easy State supports Create React App without additional configuration. Just run the following commands to get started.
npx create-react-app my-app
cd my-app
npm install react-easy-state
npm start
You need npm 5.2+ to use npx.
Easy State consists of two functions:
store
turns your objects into observable state stores.view
makes your components reactive. Reactive components re-render when stores are mutated.
store
creates a state store from the passed object and returns it. State stores are just like normal JS objects. (To be precise, they are transparent proxies of the original object.)
import { store } from 'react-easy-state'
const user = store({
name: 'Rick'
})
// stores behave like normal JS objects
user.name = 'Bob'
Store methods are automatically bound to the store to make passing them as callbacks easier.
Wrapping your components with view
turns them into reactive views. A reactive view re-renders whenever a store - used inside its render - changes.
import React, { Component } from 'react'
import { view, store } from 'react-easy-state'
const user = store({
name: 'Bob'
})
class HelloComp extends Component {
onChange (ev) {
user.name = ev.target.value
}
// the render is triggered whenever user.name changes
render () {
return (
<div>
<input value={user.name} onChange={this.onChange} />
<div>Hello {user.name}!</div>
</div>
)
}
}
// the component must be wrapped with `view`
export default view(HelloComp)
Make sure to wrap all of your components with view
- including stateful and stateless ones. If you do not wrap a component, it will not properly render on store mutations.
View methods are automatically bound to the component to make passing them as callbacks easier.
Usage as a decorator
view
can also be used as a class decorator with the @view
syntax. You can learn more about decorators here.
import React, { Component } from 'react'
import { view, store } from 'react-easy-state'
const user = store({
name: 'Bob'
})
@view
export default class HelloComp extends Component {
onChange (ev) {
user.name = ev.target.value
}
render () {
return (
<div>
<input value={user.name} onChange={this.onChange} />
<div>Hello {user.name}!</div>
</div>
)
}
}
Decorators are not a standardized JavaScript feature and create-react-app does not support them yet.
A singleton global store is perfect for something like the current user, but sometimes having local component states is better. Just create a store as a component property in these cases.
import React, { Component } from 'react'
import { view, store } from 'react-easy-state'
class ClockComp extends Component {
clock = store({
time: new Date()
})
componentDidMount () {
setInterval(() => this.clock.time = new Date(), 1000)
}
// the render is triggered whenever this.clock.time changes
render () {
return <div>{this.clock.time}</div>
}
}
// the component must be wrapped with `view`
export default view(ClockComp)
That's it! You know everything to master React state management.
Easy State monitors which store properties are used inside each component's render method. If a store property changes, the relevant renders are automatically triggered. You can do anything with stores without worrying about edge cases. Use nested properties, expando properties, arrays, ES6 collections or getters/setters - as a few examples. Easy State will monitor their mutations and trigger renders when needed. (Big cheer for ES6 Proxies!)
Triggered renders are passed to React for execution, there is no forceUpdate
behind the scenes. This means that component lifecycle hooks behave as expected and that React Fiber works nicely together with Easy State. On top of this, you can use your favorite testing frameworks without any added hassle.
Beginner
- Clock Widget (source): a reusable clock widget with a tiny local state store.
- Stopwatch (source) (tutorial): a stopwatch with a mix of normal and computed state properties.
Advanced
- TodoMVC (source): a classic TodoMVC implementation with a lot of computed data and implicit reactivity.
- Contacts Table (source): a data grid implementation with a mix of global and local state.
- Node: 6 and above
- Chrome: 49 and above
- Firefox: 38 and above
- Safari: 10 and above
- Edge: 12 and above
- Opera: 36 and above
- React Native: iOS 10 and above and Android with community JSC
- IE is not supported and never will be
This library is based on non polyfillable ES6 Proxies. Because of this, it will never support IE.
React Native is supported on iOS and Android is supported with the community JavaScriptCore. Learn how to set it up here. It is pretty simple.
You can compare Easy State with plain React and other state management libraries with the below benchmarks. It performs a bit better than MobX and a bit worse than Redux.
Under the hood Easy State uses the @nx-js/observer-util library, which relies on ES6 Proxies to observe state changes. Thanks to the Proxies it doesn't have edge cases. You can write any JS code without worrying about your render functions. This blog post gives a little sneak peek under the hood of the observer-util
.
This library detects if you use ES6 or commonJS modules and serve the right format to you. The exposed bundles are transpiled to ES5 to support common tools - like UglifyJS minifying. If you would like a finer control over the provided build, you can specify them in your imports.
react-easy-state/dist/es.es6.js
exposes an ES6 build with ES6 modules.react-easy-state/dist/es.es5.js
exposes an ES5 build with ES6 modules.react-easy-state/dist/cjs.es6.js
exposes an ES6 build with commonJS modules.react-easy-state/dist/cjs.es5.js
exposes an ES5 build with commonJS modules.
If you use a bundler, set up an alias for react-easy-state
to point to your desired build. You can learn how to do it with webpack here and with rollup here.
Contributions are always welcome. Just send a PR against the master branch or open a new issue. Please make sure that the tests and the linter pass and the coverage remains decent. Thanks!