Simplifying applications with Cerebral

Simplifying applications with Cerebral

Cerebral is a brain for your state based applications. It branches out for different implementations (react, angular, baobab, immutablejs), and comes in pluggable views, models and different backends. However, all of it connect to the core cerebral repository.

What is cerebral?

Cerebral's strength is that it is really easy to grasp. There are some great videos of it in action at the cerebral website - and the docs will clear up a lot of the finer points, however I will try and cover the basics.

Cerebral is the controller in your app. It contains a single state tree that defines your application. With the magic of an automatically re-rendering view layer like react, modifying this state tree automatically updates the look of your website or application.

Signals

Signals are simply a named collection of 'actions', and the only part of cerebral you generally interact with from your view layer.

You would call something like the following in your view layer:
signals.clickedButton({ id: 1 })

With this particular signal looking like this:

const clickedButton = [
  setLoading(true),
  [
    fetchValue, {
      success: [setLoading(false), setData],
      error: [displayErrors, setLoading(false)]
    }
  ]
];

Each action is called one after the other, until you start getting into asynchronous territory (denoted by the array around your signal), in which case the actions wait for completion. From here you have the power of output paths - allowing success or failure, or custom paths. From there it continuous synchronously with other actions (unless of course you branch off asynchronously again)

Actions

Actions are small pieces of logic that will end up driving your application. It generally does one of two things:

  • Update your applications state
  • Run an asynchronous task to prepare data for another action

An example action:

export function myAsyncAction({ input, state, output, services }) {
  const {myValue} = input;

  services.myModel.fetchAsynchronousValue()
    .then((response) => {
      // Take the success path, actions will receive the
      // hypothetical response object as its input 
      output.success(response);
    })
    .catch(() => {
      // Takes the error path. You could use this to update
      // your state to display that an error had occurred, 
      // for example
      output.error({ message: "Failed!" });
    });
}

export function mySyncAction({ input, state }) {
  const {dataFromPreviousAction} = input;
  state.set("data", dataFromPreviousAction);
}

Actions are simple functions, with a single object containing currently 4 different keys:

  • Input - contains values passed in from the signal directly, or from the previous action
  • State - gives you access to methods related to changing your single application state tree. Usually this is baobab, but can be other libraries like immutable js
  • Output - allows you to output data for the next action, and/or set which path it should take in the signal
  • Services - services registered in cerebral are accessible here. You can use this to access API models or third party libraries. Doing it this way makes these actions easier to test.

It's worth noting that an asynchronous task can not set state, it must pass output to a synchronous task to set state. So an asynchronous workflow might look like the following:

const buttonClicked = [
  setLoading(true),
  [
    fetchContacts, { 
      success: [setActiveContacts], 
      error: [displayErrors]
    }
  ],
  setLoading(false)
];
Show me a codebase!

Alright, alright. Check out the website's source code! It's written with cerebral, react and baobab, and will show how the principles above all work together.

https://github.com/cerebral/cerebral-website/tree/master/app

Why Cerebral?

I don't believe in one framework being better than another - there are many other ways of structuring your applications, from giant monolithic frameworks, to build it yourself vanilla javascript. Personally, I like structuring together many smaller libraries to give me exactly what I want and as little more as possible.

Cerebral is small to begin with, and only grows as much as you let it. It remains just a place to store your application logic, but does not specify how you get your data, what your view layer is, or even where you store your data.

I think one of the bigger reasons I like it is it's fairly quick to understand and to get others on board. Go over the docs a few times, and have a quick play around and any developer should be able to pick it up and understand it quickly.

Other reasons:

  • A really sweet chrome extension for debugging your app
  • Actions make for really great, re-useable pieces of logic for both fetching data and setting data
  • Following signal logic is as simple as following your imports, or even just reading the names of your actions
  • A very friendly, and active community. You can ask for help on discord