Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

cerebral

Package Overview
Dependencies
Maintainers
1
Versions
638
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cerebral

A modern application framework for React

  • 0.2.0
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
4.8K
decreased by-17.52%
Maintainers
1
Weekly downloads
 
Created
Source

cerebral (WIP) build status

A modern application framework for React

Video Preview at: Immutable-store gets signals and a time machine

More video preview at: Cerebral - a React framework in the making

| API |

Cerebral - The abstraction

Read this article introducing Cerebral: Cerebral developer preview (WIP)

Short history

I have been writing about, researching and developing both traditional and Flux architecture for quite some time. Look at my blog www.christianalfoni.com for more information. Though I think we are moving towards better abstractions for reasoning about our applications there are core issues that are yet to be solved. This library is heavily inspired by articles, videos, other projects and my own experiences building applications.

Contributors

  • Logo and illustrations - Petter Stenberg Hansen
  • Article review - Jesse Wood

Thanks guys!

Core features

  • An architecture inspired by Flux and Baobab
  • A single object for all application state
  • One way flow of state
  • Has complete control of your application state flow using signals
  • Can retrace state changes live in the UI
  • Specific concepts for handling asynchronous code and relational data
  • Immutable data
  • A functional approach to interactions
  • Gives errors on invalid code
  • Requires React as your UI layer

Creating an application

All examples are shown in ES6 code.

You can use the cerebral-boilerplate to quickly get up and running. It is a Webpack and Node setup.

Create a cerebral

cerebral.js

import Cerebral from 'cerebral';

let state = {
  todos: [],
  newTodoTitle: ''
};

export default Cerebral(state);

You instantiate a cerebral simply by passing an object representing the initial state of the cerebral.

Create a signal

main.js

import cerebral from './cerebral.js';
import changeNewTodoTitle from './actions/changeNewTodoTitle.js';
import addNewTodo from './actions/addNewTodo.js';

cerebral.signal('newTodoTitleChanged', changeNewTodoTitle);
cerebral.signal('newTodoSubmitted', addNewTodo);

A signal can be triggered by any component. The name of a signal should be "what triggered the signal". A signal can have multiple actions related to them, always being triggered one after the other, in the order your provide the actions.

Create actions

actions/changeNewTodoTitle.js

let changeNewTodoTitle = function (cerebral, event) {
  cerebral.set('newTodoTitle', event.target.value);
};

default export changeNewTodoTitle;

actions/addNewTodo.js

let addNewTodo = function (cerebral) {
  cerebral.push('todos', {
    title: cerebral.get('newTodoTitle'),
    created: Date.now()
  });
  cerebral.set('newTodoTitle', '');
};

default export addNewTodo;

An action is named "what to do with the signal". An action is by default synchronous and any value returned will be passed to the next action. An action always receives the cerebral as the first argument.

Create a UI

App.js

import React from 'react';
import mixin from 'cerebral/mixin';

let App = React.createClass({
  mixins: [mixin],
  getCerebralState() {
    return ['todos', 'newTodoTitle'];
  },
  renderItem(todo, index) {
    return (
      <li key={index}>{todo.title}</li>
    );
  },
  onNewTodoSubmitted(event) {
    event.preventDefault();
    this.signals.newTodoTitleSubmitted();
  },
  onNewTodoTitleChanged(event) {
    this.signals.newTodoTitleChanged(event.target.title);
  },
  render() {
    return (
      <div>
        <form onSubmit={this.onNewTodoSubmitted}>
          <input type="text" onChange={this.onNewTodoTileChanged} value={this.state.newTodoTitle}/>
        </form>
        <ul>
          {this.state.todos.map(this.renderItem)}
        </ul>
      </div>
    );
  }
});

export default App;

The mixin allows you to expose state from the cerebral to the component. You do that by returning an array with paths or an object with key/path. The mixin includes a PureRenderMixin that checks changes to the state and props of the component, to avoid unnecessary renders. This runs really fast as the cerebral is immutable.

Inject the cerebral into the app

main.js

import React from 'react';
import cerebral from './cerebral.js';
import changeNewTodoTitle from './actions/changeNewTodoTitle.js';
import addNewTodo from './actions/addNewTodo.js';
import App from './App.js';

cerebral.signal('newTodoTitleChanged', changeNewTodoTitle);
cerebral.signal('newTodoTitleSubmitted', addNewTodo);

let Wrapper = cerebral.injectInto(App);

React.render(<Wrapper/>, document.querySelector('#app'));

To expose the cerebral to the components you need to inject it. The returned wrapper can be used to render the application. This is also beneficial for isomorphic apps.

Handle async actions

actions/addNewTodo.js

let addNewTodo = function (cerebral) {
  let todo = {
    ref: cerebral.ref(),
    $isSaving: true,
    title: cerebral.get('newTodoTitle'),
    created: Date.now()
  };
  cerebral.push('todos', todo);
  cerebral.set('newTodoTitle', '');
  return todo;
};

export default addNewTodo;

actions/saveTodo.js

import ajax from 'ajax';

let saveTodo = function (cerebral, todo) {
  return ajax.post('/todos', {
      title: todo.title,
      created: todo.created
    })
    .then(function (result) {
      return {
        ref: todo.ref,
        $isSaving: false
      };
    })
    .fail(function (error) {
      return {
        ref: todo.ref,
        $isSaving: false,
        $error: error
      };
    });
};

export default saveTodo;

actions/updateTodo.js

let updateSavedTodo = function (cerebral, updatedTodo) {

  let todo = cerebral.getByRef('todos', updatedTodo.ref);
  cerebral.merge(todo, updatedTodo);

};

default export updateSavedTodo;

main.js

import React from 'react';
import cerebral from './cerebral.js';
import changeNewTodoTitle from './actions/changeNewTodoTitle.js';
import addNewTodo from './actions/addNewTodo.js';
import saveTodo from './actions/saveTodo.js';
import updateTodo from './actions/updateTodo.js';
import App from './App.js';

cerebral.signal('newTodoTitleChanged', changeNewTodoTitle);
cerebral.signal('newTodoTitleSubmitted', addNewTodo, saveTodo, updateTodo);

App = cerebral.injectInto(App);

React.render(<App/>, document.body);

When you return a promise from an action it will become an async action. The next action will not be triggered until the async action is done. The value you return in a promise is passed to the next action. You will not be able to remember during an async action, but you will be able to remember synchronously when it is done. Any mutations to the cerebral outside of a signal or in an async callback will throw an error.

Note that values returned from actions will be frozen. You can not change them in the next action. This is due to Cerebrals immutable environment. You can use cerebral.ref() to create references on objects to extract them from the cerebral.

Map

cerebral.js

import Cerebral from 'cerebral';

var state = {
  todos: [],
  visibleTodos: [],
  newTodoTitle: ''
};

export default Cerebral(state);

main.js

import React from 'react';
import cerebral from './cerebral.js';
import changeNewTodoTitle from './actions/changeNewTodoTitle.js';
import addNewTodo from './actions/addNewTodo.js';
import saveTodo from './actions/saveTodo.js';
import updateTodo from './actions/updateTodo.js';
import App from './App.js';

cerebral.signal('newTodoTitleChanged', changeNewTodoTitle);
cerebral.signal('newTodoTitleSubmitted', addNewTodo, saveTodo, updateTodo);

cerebral.map('visibleTodos', ['todos'], function (cerebral, refs) {
  return refs.map(function (ref) {
    return cerebral.getByRef('todos', ref);
  });
});

App = cerebral.injectInto(App);

React.render(<App/>, document.body);

Map lets you compose state. This is extremely handy with relational data. If you have an array of todos and only want to show some of them and you want the shown todos to keep in sync with the original array of todos, map will help you. When calling the map method you point to the state value you want to map over, what state it depends on and a function that returns the new state.

Just like our own memory can have complex relationships, so can our application state. Our brain can even create memories if something is missing, this is also possible with mapping. An example of this is if each todo has an authorId. If the author is not in our state the map callback has to create a temporary state while we go grab the real state from the server. This can be expressed like:

cerebral.map('visibleTodos', ['todos', 'authors'], function (cerebral, refs) {

  return refs.map(function (ref) {

    let todo = cerebral.getByRef('todos', ref).toJS();
    todo.author = cerebral.get('authors')[todo.authorId];

    if (!todo.author) {
      todo.author = {
        id: todo.authorId,
        $isLoading: true
      };
      cerebral.signals.missingAuthor(todo.authorId);
    }

    return todo;

  });

});

The missingAuthor signal could go grab the author from the server and update the authors map. That would trigger the map callback again and the UI would rerender.

Remember

What makes cerebral truly unique is how you are able to debug it. The signals implementation gives cerebral full control of your state flow, even asynchronous flow. That way it is very easy to retrace your steps. By using the built in debugger you can reproduce states very easily and use the logging information to identify how state flows and what changes are made to the state.

FAQs

Package last updated on 14 May 2015

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc