react-elm

Elm reactive programming model for ReactJS


Keywords
reactjs, react, elm, elm-lang, frp
License
ISC
Install
npm install react-elm@0.0.4

Documentation

react-elm

Elm reactive programming model for ReactJS

The goal of this library is to imitate architecture used in Elm programming language where you break your application in three parts (update, model, view).

This is a simple Counter component.

Counter.js

import React from 'react'
import { Address, Action } from 'react-elm'

export function view(props) {
  return (
    <div>
      <button onClick={ (e) => Address.send(props.address, new Action("INC")) }>Inc</button>
      <span>{props.model}</span>
      <button onClick={ (e) => Address.send(props.address, new Action("DEC")) }>Dec</button>
    </div>
  );
}

export function update(action, model) {
  switch(action.type) {
    case "INC":
      return model + 1;
    case "DEC":
      return model - 1;
  }

  return model;
}

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { App } from 'react-elm'
import * as Counter from './Counter';

ReactDOM.render(
  <App model={0} update={Counter.update} view={Counter.view} />,
  document.getElementById('container')
);

Also this pattern can be reused infinitely in order to aggragate components.

Here an example aggregating two Counter components

TwoCounters.js

import React from 'react'
import { Address, Action } from 'react-elm'
import * as Counter from './Counter';

export class Model {
  constructor(counter1, counter2) {
    this.counter1 = counter1;
    this.counter2 = counter2;
  }
}

export function view(props) {
  return (
    <div>
      <Counter.view address={ Address.forwardTo(props.address, Action.wrapper("COUNTER1")) } model={ props.model.counter1 } />
      <Counter.view address={ Address.forwardTo(props.address, Action.wrapper("COUNTER2")) } model={ props.model.counter2 } />
      <button onClick={ (e) => Address.send(props.address, new Action("RESET")) }>Reset</button>
    </div>
  );
}

export function update(action, model) {
  switch(action.type) {
    case "COUNTER1":
      return new Model(Counter.update(action.wrapped, model.counter1), model.counter2);

    case "COUNTER2":
      return new Model(model.counter1, Counter.update(action.wrapped, model.counter2));

    case "RESET":
      return new Model(0, 0);
  }
  return model;
}

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { App } from 'react-elm'
import * as TwoCounters from './TwoCounters';

ReactDOM.render(
  <App model={new TwoCounters.Model(0, 0)} update={TwoCounters.update} view={TwoCounters.view} />,
  document.getElementById('container')
);

Now a more fancy example. Here we are going to create an dynamic list of Counters.

ListCounters.js

import React from 'react'
import { Address, Action } from 'react-elm'
import * as Counter from './Counter';

export class Model {
  constructor(counters) {
    this.counters = counters;
  }
}

export function view(props) {
  var counters = props.model.counters.map(function(item, index) {
      return (<Counter.view key={ index } address={ Address.forwardTo(props.address, Action.wrapper("CHILD", {index})) } model={ item } />)
  });

  return (
    <div>
      <button onClick={ (e) => Address.send(props.address, new Action("ADD")) }>Add</button>
      <div>{counters}</div>
    </div>
  );
}

export function update(action, model) {
  switch(action.type) {
    case "CHILD":
      var counters = model.counters.map(function(item, index) {
        if(index == action.index)
          return Counter.update(action.wrapped, item);
        return item;
      });

      return new Model(counters);

    case "ADD":
      return new Model(model.counters.concat([0]));
  }

  return model;
}

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { App } from 'react-elm'
import * as ListCounters from './ListCounters';

ReactDOM.render(
  <App model={new ListCounters.Model([0, 0, 0, 0])} update={ListCounters.update} view={ListCounters.view} />,
  document.getElementById('container')
);