Skip to content

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 view function, as a plain object for rendering purposes;
  • through the sources.props stream, 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} />