GithubHelp home page GithubHelp logo

hojberg / state-trooper Goto Github PK

View Code? Open in Web Editor NEW

This project forked from swipely/state-trooper

0.0 1.0 0.0 237 KB

Centrally manage state for React applications with CSP

Home Page: https://github.com/swipely/state-trooper

License: MIT License

JavaScript 100.00%

state-trooper's Introduction

state trooper

Example Usage

Call StateTrooper.patrolRunLoop in your route handler/main app entry point

const config = {
  // describe the state for the page
  state: {
    serverReport: null,
    bio: null,
    activity: null
  },

  // describe the fetchers and persisters for each piece of state
  // fetchers and persisters are functions that should return channels
  dataStore: {
    'serverReport': { fetcher: serverReportFetcher },
    'bio': { fetcher: bioFetcher, persister: bioPersister },
    'activity': { fetcher: activityFetcher }
  }
};
const cursor = StateTrooper.patrolRunLoop(config, (cursor) => {
  // Re-render the component when state changes generate new cursors
  React.render(<Server cursor={cursor}/>, document.querySelector('body'));
});

// Render the component with the initial cursor
React.render(
  <Server cursor={cursor}/>,
  document.querySelector('body')
);

Using cursors inside of the components

const Server = React.createClass({
  render: function () {
    return (
      <div>
        <ServerReport cursor={this.props.cursor.refine('serverReport')}/>
        <Bio cursor={this.props.cursor.refine('bio')}/>
      </div>
    );
  }
});

const Bio = React.createClass({
  render: function () {
    const bio = this.props.cursor.deref();

    if (bio) {
      return (
        <form>
          <label>Name</label>
          <input type='text' value={bio.name} onChange={this.handleChange}/>
          <button onClick={this.handleSaveClick}>Save</button>
        </form>
      );
    }
    else {
      return null;
    }
  },

  handleChange: function (ev) {
    this.props.cursor.set({ name: ev.target.value });
  },

  handleSaveClick: function () {
    this.props.cursor.persist();
  }
});

Fetchers will be called with a cursor and a rootCursor Persisters will be called with a cursor, a change and a rootCursor

To have your fetchers or persisters change the state, simply use one of the mutating functions of a cursor.

How state changes are applied

State change are applied one after each other. Making a change will always produce a new top level cursor with the update state. However there are cases where you can call request several mutate changes via any of the mutative functions on a cursor (set, replace, add, remove) in short succession. In cases like this the state changes might be requested before the first change is propegated. StateTrooper ensures that the changes are applied sequentially regardless of wether or not a new cursor has been added to the cursor chan yet.

Cursor API:

Given this state:

{
  foo: {
    bar: 'baz',
    beep: ['hey', 'yo']
  }
}

cursor#refine

cursor.refine('foo.bar');

Calling cursor#refine with a string path will create a new cursor for that part of the state. This is useful as you go down the component tree and the focus your component and state on a specific domain.

cursor#deref

cursor.refine('foo').deref();
// or
cursor.deref('foo');

Calling cursor#deref returns the value at the refined location. If a path is provided, the path will be used starting at the refined location.

cursor#set

cursor.refine('foo').set({ bar: 'foo' });

Calling cursor#set will merge the changes into the state at the path of the cursor. Similar to reacts setState. In this example the state change will result in:

{
  foo: {
    bar: 'foo',
    beep: ['hey', 'yo']
  }
}

set is only available on Objects/Maps

cursor#replace

cursor.refine('foo').replace('bar');

Calling cursor#replace will replace the entire subtree at the path of the cursor In this example the state change will result in:

{
  foo: 'bar'
}

cursor#remove

cursor.refine('foo.beep.0').remove();

Calling cursor#remove will remove the entire subtree at the path of the cursor. This works on both arrays and objects. In this example the state change will result in:

{
  foo: {
    bar: 'baz',
    beep: ['yo']
  }
}

cursor#add

cursor.refine('foo.beep').add('yo');

Calling cursor#add will push a new value on the end of the array held by the cursor. In this example the state change will result in:

{
  foo: {
    bar: 'baz',
    beep: ['hey', 'yo', 'yo']
  }
}

add is only available on Array values.

cursor#equals

this.state.cursor.equals(newState.cursor);

This method compares the state encapsulated by two cursors. If the cursors hold equivalent values, based on the result of an equals() method, comparison by Object.prototype.valueOf(), or comparison of key/value pairs on the object. If you are a React user, this method can be useful for implementing shouldComponentUpdate.

Monitoring Changes

StateTrooper also provides a mechanism for monitoring a piece of state and being notified when it changes. These are called "stakeouts". These are useful when an application needs to respond to a change in one piece of state, such as changing a user preference, and make further state changes.

StateTrooper.stakeout

StateTrooper.stakeout('activityReport', (update, cursor) => {
  // The `update` holds the new value and the action that caused the update
  // The `cursor` is the root cursor.
  if (update.value.somethingExcitingChanged) {
    fetch(myExcitingService)
      .then(response => response.json())
      .then(json => cursor.replace('myExcitingState', json));
  }
});

StateTrooper.patrol

  function handleActivityReportChange(update, cursor) {
    console.log('handleActivityReportChange', update);
  }

  const cursorChan = StateTrooper.patrol({
    // describe the state for the page
    state: {
      activity: null,
      activityReport: null
    },
    stakeout: {
      'activityReport': [handleActivityReportChange]
    }
  });

API Changes

A major change in the 2.x release of state-trooper is the removal of immutable-js as the internal data structure for storing state.

The following APIs changed in v2.x:

  • cursor#deref(): Values returned are not immutable-js Maps or Lists.

  • cursor#derefJS(): REMOVED

  • cursor#hasSameValue(): Renamed to cursor#equals.

  • cursor#map(): REMOVED

  • StateTrooper#stakeout(): ADDED

  • StateTrooper#patrol(): The "config" object supports a stakeout property.

state-trooper's People

Contributors

adamedgett avatar flahertyb avatar hojberg avatar newyankeecodeshop avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.