Security News
PyPI’s New Archival Feature Closes a Major Security Gap
PyPI now allows maintainers to archive projects, improving security and helping users make informed decisions about their dependencies.
Izaak Schroeder izaak.schroeder@gmail.com :name: afflux :description: Unopinionated, functionally-reactive flux patterns for React. :icons: font :source-highlighter: highlight.js :idprefix: :idseparator: - :toc: :toc-placement: preamble
{description}
image:http://img.shields.io/travis/izaakschroeder/{name}/master.svg?style=flat[foo] image:http://img.shields.io/coveralls/izaakschroeder/{name}/master.svg?style=flat[foo] image:http://img.shields.io/npm/l/{name}.svg?style=flat[foo] image:http://img.shields.io/npm/v/{name}.svg?style=flat[bar] image:http://img.shields.io/npm/dm/{name}.svg?style=flat[baz]
[abstract] {name} uses most to implement a very minimal, fast, functional-reactive style of [flux] patterns for web applications.
TODO:
{name} follows the <> mantra fairly closely by providing patterns for actions and stores. It works best with purely functional-style data representation like immutable.js.
Actions:
Example: "Create a todo with content 'x'" actions.todos.create("x")
.
Stores:
Example: "What are all the known todos?". stores.todos
State:
TIP: What about the dispatcher? There is none. Everything is a stream. You can emulate all the functionality (including waitFor
) of a dispatcher with stream combinators.
Add {name} to your project.
npm install --save afflux react
TIP: You can use the generator-{name} package with yeoman to help make action and store files automatically; there's also {name}-router for handling routing.
Actions are just functions that create promises. Typically you group your actions together logically in a class that encapsulates the relevant functionality. Actions contain no data about the state of the system.
import Promise from 'bluebird';
import { action } from 'afflux';
export default class MyActions {
constructor(api) {
this.api = api;
}
@action // <1>
foo() {
return this.api.get('/foo');
}
@action
bar() {
return Promise.reject();
}
}
<1> Use action as an ES7 decorator.
You can create free-standing functions if you wish as well.
import { action } from 'afflux';
var example = action((id) => {
return ajax().then(resp => JSON.parse(resp));
});
Stores react precisely to the results of actions. Stores do not contain the state of the system - they represent the state of the system.
Typically a store will:
You can use most combinators to achieve this.
import { await } from 'afflux';
export default class MyStore {
constructor(actions) {
this.myobject = await(actions.bar);
}
}
Higher-order components make using {name} in React views straightforward.
import { send, receive } from 'react-beam';
import { observer } from 'react-observer';
@send('stores', 'actions') <1>
class App extends Component {
render() {
return <View/>;
}
}
@receive('stores', 'actions') <1>
@observer <3>
class View extends Component {
observe(props) {
return {
myobject: props.stores.mystore.myobject
};
}
render() {
return <div>{this.props.myobject.value}</div>
}
}
<1> Use react-beam
to send
and recieve
the needed properties like stores
and actions
.
<2> Use react-observer
to watch for changes in stores and automatically re-render your component when they occur.
TIP: You can still pass stores
and actions
as part of props
when you need to -- local values override those from parents.
Server-side rendering is possible by waiting until all actions have settled and then outputting the result. Clients can then use this result by having the stores dehydrate their state on the server and rehydrate them on the client.
Every request creates new instances of actions and stores so messages and state from one request doesn't' interfere with that of another.
import { render } from 'afflux';
import express from 'express';
let app = express();
app.use((req, res, next) => {
const component = <App stores={stores} actions={actions}/>;
render(component).then(result => {
res.send(result);
}, next);
});
import { render } from 'react';
const component = <App stores={stores} actions={actions}/>;
const root = document.querySelector('#content');
render(component, root);
Observing all events:
To observe all actions, simply merge
them all together.
import { merge, observe } from 'most';
const all = merge(actions.a, actions.b, ...);
observe(all, (evt) => {
console.log('Got event', evt);
});
Waiting for other stores:
Generally when you wait for another store it's because you want to use its result as part of the new value in your store (combined with whatever actions your store observes). This can be achieved with a flatMap
combinator.
import { map, flatMap, take } from 'most';
import { partial } from 'lodash';
function compute(action, todo) {
// Do something with both action and todo
return { ... };
}
const stream = flatMap(
(result) => map(partial(compute, result), take(1, todos)),
action
);
Roughly this works as follows:
action
emits an eventtodos
compute
with both of those values and emit the resultYou can also explicitly wait for a stream by turning it into a promise with drain
.
import { drain, take } from 'most';
const result = drain(take(1, store.todos));
result.then(() => {
console.log('Finished waiting for todos');
});
{name} has no model class, however it's easy to pattern models analogous to those of backbone using [immutable]. Note that models have no methods since they cannot sensibly modify themselves - they are never attached to a store, so save
, load
, etc. are meaningless.
import { Record } from 'immutable';
class MyModel extends Record({ a: 1, b: 2 }) {
get isAdmin() {
return this.a > 3;
}
}
const test = new MyModel();
const derp = new MyModel({ a: 5, b: 7 });
console.log(derp.isAdmin);
{name} has no collection class, however it's easy to pattern collections analogous to those of backbone using [immutable] and some stream combinators. Collections are stores that accumulate changes to a set of objects over time.
import { Map, fromJS } from 'immutable';
import { merge, map, flatMapError } from 'most';
import accumulate from 'afflux/lib/combinators/accumulate';
import update from 'afflux/lib/combinators/update';
export default function createCollection(actions, initialValue) {
const updates = merge(
update((todos, todo) => todos.set(todo.id, todo), actions.create),
update((todos, todo) => todos.delete(todo.id), actions.delete),
update((_, todos) => todos, actions.rehydrate)
);
const s = flatMapError(() => updates, updates);
const initialValue = base ? fromJS(base) : Map();
const stream = map(entry => entry.toJS(), accumulate(initialValue, s));
return { ...actions, source: stream.source, id: 'todo' };
}
Sometimes information about a single entity is the result of more than one action - maybe you have chat messages that can come from an HTTP API call and from a socket.io event stream. You can use stream combinators to combine these sources for your store.
import { merge, fromEvent } from 'most';
class ChatMessageStore {
constructor(actions, io) {
const stream = merge(actions.a, fromEvent('message', io));
}
}
NOTE: Information from non-action stream sources cannot be accurately detected when using server-side rendering. This pattern should be used on the client only.
Since actions are just streams of promises, you can simply perform updates before the promise finishes - if the promise is rejected then you revert back to the old value, and if it resolves you simply ensure the current value is the actual value.
var beep = action(function(message) {
if (Math.random() > 0.5) {
Promise.resolve('beep');
} else {
Promise.reject('bop');
}
});
const optimistic = map((message), beep);
const actual =
flatMapError(e => startsWith(old, stream), stream);
const stream = merge(optimistic, actual);
You can extend this to the collection pattern to perform optimistic updates for entire collections as well.
Easy to test using any test framework that supports promises. Such a possible combination is [mocha], [chai] and [chai-as-promised].
import TodoActions from 'actions/todos.action';
describe('#create', () => {
let actions;
beforeEach(() => {
actions = new TodoActions();
});
it('should create a new todo', () => {
return expect(actions.create).to.eventually.equal({ <1>
foo: 'bar'
});
});
});
<1> Since actions return promises, we can just test the value of the promise directly.
import { never, of as just } from 'most';
import TodoStore from 'stores/todos.store';
describe('todos', () => {
it('should add created todo', () => {
const actions = { create: just({ id: 5 }), update: never };
const store = new TodoStore(actions);
// Since stores are also promises, we can just test the value of
// the promise directly.
return expect(store.todos).to.eventually.contain({ id: 5 });
});
})
Testing views is slightly more involved since React and the DOM are now involved. Stubbing out actions and stores are both straightforward, however, and follow from the previous two types of testing.
import View from './view';
import { jsdom } from 'jsdom';
import { renderComponent } from 'react';
describe('View', () => {
const html = '<!doctype html><html><body><div id="test"/></body></html>';
let view, actions, stores, document, target;
function render(view) {
return renderComponent(view, target);
}
beforeEach(() => {
document = jsdom(html);
target = document.getElementById('test');
actions = {
test: stub().returns(Promise.resolve('yes'))
}
stores = {
todos: emitter()
}
});
describe('#render', () => {
it('should add todo when add button clicked', () => {
const view = <View actions={..} stores={..}/>;
let node = render(view);
node.button.click();
expect(actions.test).to.be.calledOnce;
});
it('should display list of todos from store', () => {
const view = <View actions={..} stores={..}/>;
stores.todos.emit({ id: 5, text: "hello" });
let node = render(view);
expect(node.props.children).to.have.length(1);
});
});
});
[bibliography]
FAQs
Functional-reactive flux.
We found that afflux demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer 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 allows maintainers to archive projects, improving security and helping users make informed decisions about their dependencies.
Research
Security News
Malicious npm package postcss-optimizer delivers BeaverTail malware, targeting developer systems; similarities to past campaigns suggest a North Korean connection.
Security News
CISA's KEV data is now on GitHub, offering easier access, API integration, commit history tracking, and automated updates for security teams and researchers.