Props and State
Props and state are the two main sources of truth in a React component. In Lemni, streams of props and state are available through the sources object, what enables us to combine or compose them with other asynchronous data, such as lifecycle events or user interaction.
However, as two particularly important sources of truth, they are also passed as plain objects to the view sink, so we can easely use them in the rendering.
import { lemni } from 'lemni' const Component = lemni( (sources) => { // streams of props and state const { props, state } = sources return { // plain props and state objects // (pay atention to the destructuring of the argument) view: ({ props, state }) => ( <div> <p>{props.foo}<p/> <p>{state.bar}<p/> </div> ) } } )
Props
As showed above, there are two ways of using a component's props in Lemni:
- through the argument of the
viewfunction, as a plain object for rendering purposes; - through the
sources.propsstream, generaly used for composition of streams - as shown on the State topic bellow.
Any changes on a component's props will cause the view sink function to be called again with the updated value, as well as trigger a new emission on the sources.props stream.
State
As with props, the state of a component is assessible from the sources.state stream, as well as from the view sink argument.
Unlike props, though, the state can be updated from inside the component who owns it. Since this is a side effect, it must be done through a sink. In this case, the stateReducer sink.
A initialState sink may also be used to set the initial state when needed.
import { lemni } from 'lemni' import xs from 'xstream' const Incrementer = lemni(sources => { const onIncrement = xs.Stream.create() return { initialState: { count: 0, }, stateReducer: onIncrement.mapTo( state => ({ count: state.count + 1, }) ), // see more about the emitter in Handling Events section view: ({ state, emitter }) => ( <p>Count: {state.count}</p> <button onClick={emmiter(onIncrement).emit}> Increment </button> ) } })
The stateReducer sink is a stream of state reducers: functions which receive the latest state as argument and return a new one.
For a more complex example, we may compose state and props streams to achieve a more generic incrementer, whose step size are specified via props:
import { lemni } from 'lemni' import sampleCombine from 'xstream/extra/sampleCombine' import xs from 'xstream' const Incrementer = lemni( (sources) => { const onIncrement = xs.Stream.create() return { initialState: { count: 0 } stateReducer: onIncrement .compose(sampleCombine( sources.props .map(props => props.step) )) .map(([_clickEvent, step]) => state => ({ count: state.count + step }) ) view: ({ props, state, emitter }) => ( <div> <p>Count: {state.count}</p> <button onClick={emitter.emit(onIncrement)}> Increment {props.step} </button> </div> ) } } )
Now we can render an incrementer which jump two units per click:
<Incrementer step={2} />