/ts-elmish

Elmish architecture in Typescript

Primary LanguageTypeScriptMIT LicenseMIT

ts-elmish

npm version build publish codecov Type Coverage npm

Elmish architecture in Typescript


Features

  • minimalistic, no dedicated ecosystem approach
  • unobtrusive, doesn't capture app composition root
  • modular, supports different view layers and effect handling strategies
  • testable, encourages view/logic/effect separation

Packages

@ts-elmish/core elmish runtime dependencies
@ts-elmish/basic-effects effects from functions dependencies
@ts-elmish/railway-effects ROP-powered effects dependencies
@ts-elmish/react react view layer dependencies
@ts-elmish/mithril mithril view layer dependencies
@ts-elmish/debugger redux-devtools integration dependencies

Getting started

At first you have to choose an effect handling strategy - currently there are two options:

  • basic-effects - effects are created from sync or async functions just like in original Elmish, all errors have unknown type. Success and failure handlers are both optional, if failure handler is provided an error will be caught with try/catch statement.
  • railway-effects - this approach embraces result type and railway oriented programming, effects are created from functions that return values of Result or AsyncResult types provided by ts-railway package, all errors are properly typed. Success handler is optional, but failure hanlder is either required or prohibited (when result error type is never). Despite this approach is quite handy for enforcing domain error handling, it has some catches too.

Then just add ts-elmish packages to your project:

  • basic-effects with react/react-native:

    npm i @ts-elmish/core @ts-elmish/react @ts-elmish/basic-effects
    
  • railway-effects with react/react-native:

    npm i @ts-elmish/core @ts-elmish/react @ts-elmish/railway-effects ts-railway
    
  • basic-effects with mithril:

    npm i @ts-elmish/core @ts-elmish/mithril @ts-elmish/basic-effects
    
  • railway-effects with mithril:

    npm i @ts-elmish/core @ts-elmish/mithril @ts-elmish/railway-effects ts-railway
    

Useful generic purpose modules:

Basic example

import m, { Component } from 'mithril'
import { ElmishAttrs, createElmishRootComponent } from '@ts-elmish/mithril'
import { Effect } from '@ts-elmish/basic-effects'

type State = {
  readonly count: number
}

type Action = 'increment' | 'decrement'

const init = (): State => {
  return { count: 0 }
}

const update = ({ count }: State, action: Action): State => {
  switch (action) {
    case 'increment':
      return { count: count + 1 }

    case 'decrement':
      return { count: count - 1 }
  }
}

const Counter: Component<ElmishAttrs<State, Action>> = {
  view: ({ attrs: { count, dispatch } }) =>
    m('div', [
      m('div', count),
      m('button', { onclick: () => dispatch('increment') }, '+'),
      m('button', { onclick: () => dispatch('decrement') }, '-')
    ])
}

const App = createElmishRootComponent({
  init: () => [init(), Effect.none<Action>()],
  update: (state, action) => [update(state, action), Effect.none()],
  view: Counter
})

m.mount(document.body, {
  view: () => m(App, {})
})

Learning ts-elmish

Due to small size it is worth just to look at the code. Also there is a basic and advanced examples.

Links