The Elm Architecture for JavaScript
npm install raj
-
Understandable
Raj is 34 lines; 190 bytes minified. This framework can fit in your head or even a tweet. -
Testable
Raj forces us to design for better separated concerns, simpler logic, and easier tests. -
Minimal
Raj provides a tiny foundation for libraries and applications. -
Portable
Raj is view layer agnostic. The view is a side effect of state.
Check out the homepage for resources and ecosystem packages.
A counter that increments by one every time the user confirms.
import { runtime } from 'raj'
runtime({
init: [0], // State is an integer to count
update (message, state) {
return [state + 1] // Increment the state
},
view (state, dispatch) {
const keepCounting = window.confirm(`Count is ${state}. Increment?`)
if (keepCounting) {
dispatch()
}
}
})
Note: Raj is view layer agnostic. Here we use the browser's built-in view to play the part.
Raj applications are structured as programs.
Every program begins with an initial state, which can be anything, and an optional effect.
These are put into an array which is the init
property of the program.
const init = [initialState, /* optional */ initialEffect]
"Effects" are functions which receive a function dispatch
.
Effects handle asynchronous work like data-fetching, timers, and managing event listeners.
They can pass dispatch
messages and Raj uses those to update the state.
function effect (dispatch) {
// do anything or nothing; preferably something asynchronous
// call dispatch 0, 1, or N times
dispatch(message)
}
A "message" can be anything; a server response, the current time, even undefined
.
When a message is dispatched, Raj passes that message and the current state to update
.
The update
function returns a new state and optional effect.
The business logic of the program is handled with this function.
function update (message, currentState) {
return [newState, /* optional */ effect]
}
The view
is a special effect that receives both the current state and the dispatch
function.
The view
can return anything.
For the React view layer, the view
returns React elements to be rendered.
function view (currentState, dispatch) {
// anything, depending on choice of view library
}
The init
, update
, and view
form a "program" which is just an object with those properties:
const program = {
init: [initialState, /* optional */ initialEffect],
update (message, currentState) {
return [newState, /* optional */ effect]
},
view (currentState, dispatch) {
// anything, depending on choice of view library
}
};
Building any program follows the same steps:
- Define the initial state and effect with
init
- Define the state transitions and effects with
update(message, state)
- Define the view with
view(state, dispatch)
- Tie it all together into a
program
Programs compose, so a parent program might contain child programs.
- The parent's
init
may contain the child'sinit
. - The parent's
update
may call the child'supdate
with messages for the child and the child's state. - The parent's
view
may call the child'sview
with the child's state anddispatch
.
In this way, programs most often compose into a tree structure.
The root program is passed to Raj's runtime
.
The runtime calls the program, manages its state, and runs its effects.
import { runtime } from 'raj'
import { program } from './app'
runtime(program)
The Raj by Example documentation covers this in greater detail.