https://github.com/gilbox/react-stateful-stream
floating state for reactive architects
https://github.com/gilbox/react-stateful-stream
Last synced: over 1 year ago
JSON representation
floating state for reactive architects
- Host: GitHub
- URL: https://github.com/gilbox/react-stateful-stream
- Owner: gilbox
- License: mit
- Created: 2015-08-22T08:40:37.000Z (almost 11 years ago)
- Default Branch: master
- Last Pushed: 2015-11-08T01:48:22.000Z (over 10 years ago)
- Last Synced: 2025-04-13T08:11:55.630Z (over 1 year ago)
- Language: JavaScript
- Homepage:
- Size: 176 KB
- Stars: 8
- Watchers: 2
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# react-stateful-stream
re-examining modular state.
### decorator signature:
```
@stateful(initialState[, edit][, options])
```
### example: `initialState` as object, `edit` as a string
```
import React, {Component} from 'react';
import stateful from 'react-stateful-stream';
@stateful(
{ count: 0 },
'edit')
class App extends Component {
render() {
const {count, edit} = this.props;
const incrementCount =
() => edit(state => ({count: state.count+1}));
return
count: {count}
}
}
```
### example: `initialState` as a function, `edit` as a function
alternatively, our `initialState` argument can be a function
in which case it will receive `props` and `context` as arguments.
and, our `edit` argument can be a function in which case it will
receive `edit` as it's only argument
```
import React, {Component} from 'react';
import stateful from 'react-stateful-stream';
import u from 'updeep';
const immutable = u({});
const sub = (edit, ...path) =>
transform => edit(u.updateIn(path, transform));
const increment = x => x+1;
@stateful(
({initialCount}) => immutable({
count: initialCount || 0
}),
edit => ({
editCount: sub(edit, 'count')
}))
class App extends Component {
render() {
const {count, editCount} = this.props;
return (
editCount(increment)}>
count: {count}
)
}
}
```
### `@inject` decorator
import {inject} from 'react-stateful-stream/inject';
Instead of passing `@stateful` props down the component
tree, we can use the `@inject` decorator to access
the state as long as the component is a decendant.
First, pass in an `options` object as the
third argument of `@stateful`, specifying a
`contextKey` property.
@stateful(
{ count: 0 },
edit => ({
incrementCount: () => edit(state => ({...state, count: state.count+1}))
}),
{ contextKey: 'countState' })
class App extends Component {
// ....
}
Now wherever we'd like to inject the state,
add the `@inject` decorator, utilizing the same
key from before:
@inject('countState')
class Child extends Component {
render() {
const { count,
incrementCount } = this.props.countState;
return (
increment {count}
)
}
}
Notice that `@inject` will pass the props that are
normally passed along by `@stateful` in a new prop
with the same name as the `contextKey`.
It's done this way to avoid confusion about where our
various props are coming from.
### `` component
import {Inject} from 'react-stateful-stream/inject';
Similar to the `@inject` decorator, `` allows us to inject state.
We specify the `contextKey` as a prop of ``
and the only child of `` is a function.
Into that function, `` passes two arguments: `state` and `edit`.
We use destructuring to access `count` from `state` and `incrementCount`
from `edit`;
class Child extends Component {
render() {
return (
{({count}, {incrementCount}) =>
increment {count}
)}
)
}
}
*Note: `@inject` and `` are optional. If you don't import
`react-stateful-stream/inject`, they won't be included in your bundle.*
### `@provide` decorator
import {provide} from 'react-stateful-stream/provide';
While `@inject` is about flexibility and ad-hoc state management, `@provide` is
about discipline and top-down state management. That's because
while `@inject` works with any number of `@stateful`s, `@provide`
only works with a single `@stateful` we designate the *provider*.
(It's directly inspired by the redux notion of a provider.)
We designate a `@stateful` to be a provider with the `provider`
property of the `options` (third) argument:
@stateful(
{ count: 0 },
edit => ({
incrementCount: () => edit(state => ({...state, count: state.count+1}))
}),
{ provider: true })
class App extends Component {
// ....
}
`@provide` is a 3-arity function which takes *select* and
*selectEdit* for the first two arguments. (The third argument
is the component itself)
@provide(
({count}) => ({count}),
({incrementCount}) => ({incrementCount}))
class Child extends Component {
render() {
const {count, incrementCount} = this.props;
return (
increment {count}
)
}
}
Note that our *select* argument above
({count}) => ({count})
has the following signature:
(state) => ({.........})
Use [reselect](https://github.com/rackt/reselect)
to create sophisticated, memoizing selectors.
Note that the *selectState* arg probably does not need to be
memoized since it will only be called once per component
instantiation.
Since the component above has no lifecycle methods other than
render, we can create it using a pure function instead of a `class`:
const selectCount = ({count}) => ({count});
const selectIncrementCount = ({incrementCount}) => ({incrementCount});
const Child = provide(selectCount, selectIncrementCount)(
({count, incrementCount}) =>
increment {count}
)
You might have noticed that `provide` (or `@provide`) automatically
combines the result of *select* and *selectState* to create the props object.
Internally, here's what that looks like:
render() {
return (
)
}
If this does not provide enough flexibility, use the ``
component instead.
### `` component
import {Provide} from 'react-stateful-stream/provide';
`` offers convenience, flexibility, and performance
optimizations (in certain situations) over `@provide`.
const selectCount = ({count}) => ({count});
const selectIncrementCount = ({incrementCount}) => ({incrementCount});
const Child = () =>
{({count}, {incrementCount}) =>
increment {count}
}
The `children` prop of `` is a function that receives
`select(state)` and `selectEdit(edit)` args. Where `select`
is a function that you (optionally) pass in the `select` prop.
Likewise, `selectEdit` is a function that you (optionally) pass
in the `selectEdit` prop.
If either prop is omitted,
the identity function `x => x` is used.
*Note: `@provide` and `` are optional. If you don't import
`react-stateful-stream/provide`, they won't be included in your bundle.*
### `Atom`
We can import the `Atom` class and do something
with it.
```
import Atom from 'react-stateful-stream/Atom';
import {on} from 'flyd';
const atom = new Atom({count: 0});
on(state => console.log('changed: ', state.count), atom.didSetState$);
atom.updateState(state => ({count: state.count+1}));
// => "changed: 1"
```
## react-native support
Same as above, just change the imports by appending `/native`:
import stateful from 'react-stateful-stream/native';
import {inject, Inject} from 'react-stateful-stream/inject/native';
import {provide, Provide} from 'react-stateful-stream/provide/native';