Code Monkey home page Code Monkey logo

raj's Introduction

Raj

The Elm Architecture for JavaScript

npm install raj

npm Build Status Greenkeeper badge

Features

  • 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.

Example

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.

Architecture

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:

  1. Define the initial state and effect with init
  2. Define the state transitions and effects with update(message, state)
  3. Define the view with view(state, dispatch)
  4. 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's init.
  • The parent's update may call the child's update with messages for the child and the child's state.
  • The parent's view may call the child's view with the child's state and dispatch.

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.

raj's People

Contributors

andrejewski avatar greenkeeper[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

raj's Issues

Redundant view updates?

If something has an effect, and the effect dispatches another update, the view effectively ends up getting updated twice.

If that effect has an effect, three updates, and so on.

Is this by design?

Redux DevTools support (via HOP)

I've successfully connected Raj to the Redux DevTools extension for Chrome.

function connectDevToolsTo(program) {
    const extension = window.__REDUX_DEVTOOLS_EXTENSION__;

    if (!extension) {
        console.error("Redux DevTools not available.");
        return program;
    }

    const devTools = extension.connect();
    
    const setState = {};
    
    const [state, effect] = program.init;

    return {
        ...program,
        init: [state, dispatch => {
            devTools.init(state);

            devTools.subscribe((message) => {
                if (message.type === 'DISPATCH' && message.state) {
                    dispatch({ type: setState, state: JSON.parse(message.state) });
                }
            });   

            if (effect) {
                effect(dispatch);
            }
        }],
        update: (message, state) => {
            if (message.type === setState) {
                return [message.state];
            } else {
                const change = program.update(message, state);
                devTools.send(message, change[0]);
                return change;
            }
        }
    };
}

I know you have your own dedicated devtool for Raj, but this extension is pretty rad - what with the time-travel feature and all, which works with this little HOP attached. All the cool kids are using it ;-)

Action required: Greenkeeper could not be activated 🚨

🚨 You need to enable Continuous Integration on all branches of this repository. 🚨

To enable Greenkeeper, you need to make sure that a commit status is reported on all branches. This is required by Greenkeeper because we are using your CI build statuses to figure out when to notify you about breaking changes.

Since we did not receive a CI status on the greenkeeper/initial branch, we assume that you still need to configure it.

If you have already set up a CI for this repository, you might need to check your configuration. Make sure it will run on all new branches. If you don’t want it to run on every branch, you can whitelist branches starting with greenkeeper/.

We recommend using Travis CI, but Greenkeeper will work with every other CI service as well.

Once you have installed CI on this repository, you’ll need to re-trigger Greenkeeper’s initial Pull Request. To do this, please delete the greenkeeper/initial branch in this repository, and then remove and re-add this repository to the Greenkeeper integration’s white list on Github. You'll find this list on your repo or organiszation’s settings page, under Installed GitHub Apps.

Add common monads

raj/types

This module exposes the common monads most people will use: Maybe (aka Optional) and Result (aka Either). The .case I think needs a better name, and it can't be .match.

import message from 'raj/message'

export const Maybe = (function () {
  const Just = message()
  const Nothing = message()
  const Maybe = message.union([Just, Nothing])
  Maybe.Just = Just
  Maybe.Nothing = Nothing
  Maybe.case = (val, justCase, nothingCase) => {
    return Maybe.match(val, [
      Just, x => justCase(x),
      Nothing, () => nothingCase()
    ])
  }
})()

export const Result = (function () {
  const Err = message()
  const Ok = message()
  const Result = message.union([Err, Ok])
  Result.Err = Err
  Result.Ok = Ok
  Result.case = (val, errCase, okCase) => {
    return Result.match(val, [
      Err, err => errCase(err),
      Ok, ok => okCase(ok)
    ])
  }
  return Result
})()

The problem is that this module may become to big if we add other methods to the monads, hence my hesitation to add them here and not in a dedicated raj-types module.

Add multi-matching on message unions

We often need to combine multiple union types in a .match. Currently we have to write:

const returnValue = Page.match(page, [
  PersonListPage, page => Msg.match(msg, [
    PersonListMsg, msg => [state],
    () => [state]
  ]),
  () => Msg.match(msg, [
    PersonListMsg, msg => [state],
    () => [state]
  ])
])

raj/multi-match

This module would expose a multiMatch(unions, values, cases) function which would be used like below:

const returnValue = multiMatch([Page, Msg], [page, msg], [
  [PersonListPage, PersonListMsg], ([page], [msg]) => [state],
  [PersonListPage,], ([page], _) => [state], // these just demonstrate wildcards
  [, PersonListMsg], (_, [msg]) => [state], // these just demonstrate wildcards
  () => [state] // catch-all still available
])

Problems:

  • This syntax is very terse and it is hard to tell what is going on. Destructuring makes this tolerable, but there are tons of arrays flying around. Wildcards are also hard to spot.

Alternate syntax:

const returnValue = multiMatch([Page, Msg], [page, msg], [
  PersonListPage, [
    PersonListMsg, ([page], [msg]) => [state],
    ([page], _) => [state]
  ],
  void 0, [
    PersonListMsg, (_, [msg]) => [state]
  ],
  () => [state]
])

Problems:

  • Hard to write, I know. Does not fix the overuse of destructuring from the above syntax.

Current thinking: do not add until we have a good syntax because the current way of doing things is not too bad.

Typescript typings

I like the look of raj and want to try it out :-). However since I use typescript I cannot try anything until I have types. So here is my initial attempt at typings for raj:

export type Change<S, M> = [S, Effect<M>?];

export interface Effect<M> {
  (dispatch: Dispatch<M>): void;
}

export interface Update<M, S> {
  (message: M, state: S): Change<S, M>;
}

export interface View<M, S, V> {
  (state: S, dispatch: Dispatch<M>): V;
}

export interface Dispatch<M> {
  (message?: M): void;
}

export interface Done<S> {
  (state: S): void;
}

export interface Program<S, M, V> {
  readonly init?: Change<S, M>;
  readonly update: Update<M, S>;
  readonly view: View<M, S, V>;
  readonly done?: Done<S>;
}

export function runtime<S, M, V>(program: Program<S, M, V>): void;

You can see them in use in my experiment repo.

Any feedback on the typings are welcome. It would be nice if they could be added to the package once they are reviewed fully.

Create routing package

We need a routing solution for single-page apps. Below is the husk of a router implementation. The internal router is using the method names used in rove but does not matter from a user perspective. Describing the parser argument will also be a challenge, but if we use rove we can make an arbitrary, but deterministic, mapping between user-defined messages and name strings.

raj-url-router

export default class Router {
  constructor (parser) {
    this._router = this._createRouter(parser) // TODO: the hard part
    this._isSubscribed = false
  }

  href (routeMsg) {
    return this._router.getRouteUrl(routeMsg)
  }

  subscribe (setRouteMsg, cancelMsg) {
    // TODO: throw if _isSubscribed is true
    this._isSubscribed = true

    let isDispatched = false
    return dispatch => {
      // TODO: throw if called more than once
      isDispatched = true
      this._dispatch = dispatch

      const offNavigation = this._router.onNavigation(routeMsg => {
        dispatch(setRouteMsg(routeMsg))
      })

      let isCancelled = false
      function cancel () {
        // TODO: throw if called more than once
        isCancelled = true
        this._isSubscribed = false
        offNavigation()
      }

      dispatch(cancelMsg(cancel))
      dispatch(setRouteMsg(this._router.getCurrentRoute()))
    }
  }
}

update examples to use the latest tagmeme

Hi!

I'm really liking the look of raj, and still trying to wrap my head around it.

I was looking over the examples, and noticed that they use an older version of tagmeme:

export const ChangeQuery = tag()
export const ReceiveResults = tag()
export const ReceiveError = tag()
export const Search = tag.union([
  ChangeQuery,
  ReceiveError,
  ReceiveResults
])

where the new version of tagmeme no longer uses tag():

import {union} from 'tagmeme'

const Result = union(['Ok', 'Err'])

I would be happy to submit a PR updating the examples, but I'm not quite sure I fully understand yet. I'll take a swing and update w/ any feedback you might have.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.