JSPM

  • Created
  • Published
  • Downloads 12093155
  • Score
    100M100P100Q208363F
  • License MIT

An experiment in fully hot-reloadable Flux

Package Exports

  • redux
  • redux/package.json

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (redux) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

redux

An experiment in fully hot-reloadable Flux.

The API might change any day.
Don't use in production.

Why another Flux framework?

Read The Evolution of Flux Frameworks for some context.

Design Goals

  • Hot reloading of everything.
  • A hook for the future devtools to "commit" a state, and replay actions on top of it during hot reload.
  • No createAction, createStores, wrapThisStuff. Your stuff is your stuff.
  • I don't mind action constants. Seriously.
  • Embrace decorators for React components.
  • Keep Flux lingo. No cursors or observables in core.
  • Have I mentioned hot reloading yet?

Demo

git clone https://github.com/gaearon/redux.git redux
cd redux
npm install
npm start

What's it look like?

Actions

// Still using constants...
import {
  INCREMENT_COUNTER,
  DECREMENT_COUNTER
} from '../constants/ActionTypes';

// But action creators are pure functions returning actions
export function increment() {
  return {
    type: INCREMENT_COUNTER
  };
}

export function decrement() {
  return {
    type: DECREMENT_COUNTER
  };
}

// Can also be async if you return a function
// (wow, much functions, so injectable :doge:)
export function incrementAsync() {
  return dispatch => {
    setTimeout(() => {
      dispatch(increment());
    }, 1000);
  };
}

// Could also look into state in the callback form
export function incrementIfOdd() {
  return (dispatch, state) => {
    if (state.counterStore.counter % 2 === 0) {
      return;
    }

    dispatch(increment());
  };
}

Stores

// ... too, use constants
import {
  INCREMENT_COUNTER,
  DECREMENT_COUNTER
} from '../constants/ActionTypes';

// but you can write this part anyhow you like:

const initialState = { counter: 0 };

function increment({ counter }) {
  return { counter: counter + 1 };
}

function decrement({ counter }) {
  return { counter: counter - 1 };
}

// what's important is that Store is a pure function too
export default function counterStore(state = initialState, action) {
  // that returns the new state when an action comes
  switch (action.type) {
  case INCREMENT_COUNTER:
    return increment(state, action);
  case DECREMENT_COUNTER:
    return decrement(state, action);
  default:
    return state;
  }
}

// bonus: no special support needed for ImmutableJS,
// just return its objects as the state.

Components

Observing a single Store

// We're gonna need some decorators
import React from 'react';
import { observes } from 'redux';

// Gonna subscribe it
@observes('counterStore')
export default class Counter {
  render() {
    const { counter } = this.props; // injected by @observes
    return (
      <p>
        Clicked: {counter} times
      </p>
    );
  }
}

Observing many Stores

// We're gonna need some decorators
import React from 'react';
import { observes } from 'redux';

// With multiple stores, you might want to specify a prop mapper as last argument.
// You can also access `props` inside the prop mapper.
@observes('counterStore', 'todoStore', (state, props) => ({
  counter: state.counterStore.counter,
  todos: state.todoStore.todos
}))
export default class TodosWithCounter {
  /* ... */
}

Performing a single Action

// We're gonna need some decorators
import React from 'react';
import { performs } from 'redux';

// Gonna inject it
@performs('increment')
export default class IncrementButton {
  render() {
    const { increment } = this.props; // injected by @performs
    return (
      <button onClick={increment}>+</button>
    );
  }
}

Performing many Actions

// We're gonna need some decorators
import React from 'react';
import { performs } from 'redux';

// With multiple actions, you might want to specify a prop mapper as last argument.
// You can also access `props` inside the prop mapper.
@performs('increment', 'decrement', (actions, props) => ({
  increment: props.invert ? actions.decrement : actions.increment,
  decrement: props.invert ? actions.increment : actions.decrement
}))
export default class IncrementButton {
  /* .... */
}

Dispatcher

Creating a hot-reloadable dispatcher

import * as stores from './stores/index';
import * as actions from './actions/index';
import { createDispatcher } from 'redux';

// Prefer to use existing dispatcher
const dispatcher =
  module.hot && module.hot.data && module.hot.data.dispatcher ||
  createDispatcher();

// Pass (potentially hot-reloaded) stores and actions
dispatcher.receive(stores, actions);

// Store the dispatcher for the next hot reload
if (module.hot) {
  module.hot.dispose(data => {
    data.dispatcher = dispatcher;
  });
}

export default dispatcher;

Attaching the dispatcher to the root component

import React from 'react';
import { provides } from 'redux';
import dispatcher from './dispatcher';

@provides(dispatcher)
export default class App {
  /* ... */
}

FAQ

How does hot reloading work?

But you're using strings for injecting actions and store state!

I'm not super happy about strings. If you find a better way, let me know and file an issue with your suggestions.

Can I use this in production?

I wouldn't. Many use cases are not be considered yet. If you find some use cases this lib can't handle yet, please file an issue.