Security News
PyPI Introduces Digital Attestations to Strengthen Python Package Security
PyPI now supports digital attestations, enhancing security and trust by allowing package maintainers to verify the authenticity of Python packages.
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 |
Read this article introducing Cerebral: Cerebral developer preview (WIP)
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.
Thanks guys!
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.
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.
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.
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.
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.
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.
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.
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.
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
A state controller with its own debugger
The npm package cerebral receives a total of 3,729 weekly downloads. As such, cerebral popularity was classified as popular.
We found that cerebral demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 6 open source maintainers collaborating on the project.
Did you know?
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.
Security News
PyPI now supports digital attestations, enhancing security and trust by allowing package maintainers to verify the authenticity of Python packages.
Security News
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.