0.2.3 • Published 9 years ago

re-structure v0.2.3

Weekly downloads
1
License
MIT
Repository
github
Last release
9 years ago

Re-structure

A pattern for structuring React applications. Heavily based on the re-frame ClojureScript project.

The re-frame README pretty much says it all. This is an approximate adaptation of that idea, meant for building JavaScript applications.

Examples

See here.

Run the examples locally:

npm install
npm run examples

Simple time/color example from reagent : http://localhost:8000/simple.bundle

TodoMVC example : http://localhost:8000/todomvc.bundle

Application "pages" example : http://localhost:8000/pages.bundle

Concepts

Application DB

Just like re-frame has one global application db structure that holds the entire application state, re-structure does too. Your application db is just a data stucture that you create and initialize yourself. You can use Immutable.js, which makes rendering more performant, but it's not required; you could use a regular JS object or any other data structure you want.

Once you initialize your db you have to register it with re-structure when bootstrapping your app:

import {initApp} from 're-structure';

// initialize the application db
let db = Immutable.fromJS({
    timer: new Date(),
    timeColor: '#f34'
});

// register application db with re-structure
initApp(db);

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

initApp accepts an optional second parameter which defines debugging options. If set to true, re-structure will log all db inputs/commands and db outputs/projections to the developer console.

For more control of what is logged, provide a configuration object of the format: {logCommands: true, logProjections: false}. To log a subset of the application db, provide a projection function as the logProjections value:

initApp(db, {logProjections: db => db.get('timer')});

In the example above, the timer value will be logged to the console when it changes.

Projections

Projections are very similar to subs in re-frame. Where in re-frame you would do this:

(register-sub
  :time-color
  (fn
    [db _]
    (reaction (:time-color @db))))

In re-structure you do this:

// project a part of the application db 
export function timeColor(db) {
    return db.get('timeColor');
}

This defines a normal function that takes the current state of the application db as a parameter and returns a "projected" view of it, exactly what the registered sub does in the re-frame example. Note that you didn't have to do anything special like register the function or wrap the result in a reaction.

Views

An example View:

import {timer} from './projections';

// a view is just a decorated React component
export default @View class Clock extends React.Component {
    // declare projections that the view depends on
    static projections = {timer};
    render() {
        {/* the current value of the projection is available as a prop */}
        return <div>{this.props.timer}</div>;
    }
}

Views have one ability that distinguishes them from normal React components: they can render projection values reactively as they change. When a View depends on a projection's results, it declares so in the static projections property. Then, whenever the projection has an updated value (according to ===), the View is automatically re-rendered and the new value is available as a prop with the same name (this.props.timer in the example above). This is along the same lines as how reactions are used in re-frame/reagent.

Beacuse Views are just decorated React components, you can still keep local state, use lifecycle methods, etc.

Projections can also be parameterized with props values:

static projections = props => ({
    page: db => db.getIn(['pages', props.pageNumber])
});

In this example, projections defines a function that takes props as a parameter and defines a page projection which projects a part of the application state (located using the props.pageNumber). Note that the projections function returns an object that looks like a normal projections definition.

Here's how we might declare a projection inline that doesn't need props:

static projections = {
    pageOne: db => db.getIn(['pages', '1'])
};

Commands

A command is a normal function that takes the current application db value and optional parameters and returns a new version of the application db, with changes applied to it. It is similar to an event handler in re-frame.

An example command:

// return a new version of db with an updated timeColor value
export function setTimeColor(db, {color}) {
    return db.set('timeColor', color);
}

To emit a command, you do just that:

import {setTimeColor} from './commands';

let ColorInput = @View class ColorInput extends React.Component {
    static projections = {timeColor};
    render() {
        return (
            <div className="color-input">
                Time Color: <input type="text"
                                   value={this.props.timeColor}
                                   onChange={e => emit(setTimeColor, {color: e.target.value})}/>
            </div>
        );
    }
}
0.2.3

9 years ago

0.2.2

9 years ago

0.2.1

9 years ago

0.2.0

9 years ago

0.1.0

9 years ago

0.0.1

9 years ago