Skip to content
forked from reduxjs/redux

An experiment in fully hot-reloadable Flux

License

Notifications You must be signed in to change notification settings

aaronjensen/redux

 
 

Repository files navigation

redux

build status npm version

An experiment in fully hot-reloadable Flux.

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

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 wrapper calls in your stores and actions. Your stuff is your stuff.
  • Super easy to test things in isolation without mocks.
  • I don't mind action constants. Seriously.
  • 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 does 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
export function incrementAsync() {
  return dispatch => {
    setTimeout(() => {
      // Yay! Can invoke sync or async actions with `dispatch`
      dispatch(increment());
    }, 1000);
  };
}


// Could also read state of a store in the callback form
export function incrementIfOdd() {
  return (dispatch, { counter }) => {
    if (counter % 2 === 0) {
      return;
    }

    dispatch(increment());
  };
}

Stores

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

// what's important is that Store is a pure function,
// and you can write it anyhow you like.

// the Store signature is (state, action) => state,
// and the state shape is up to you: you can use primitives,
// objects, arrays, or even ImmutableJS objects.

export default function counter(state = 0, action) {
  // this function returns the new state when an action comes
  switch (action.type) {
  case INCREMENT_COUNTER:
    return state + 1;
  case DECREMENT_COUNTER:
    return state - 1;
  default:
    return state;
  }

  // BUT THAT'S A SWITCH STATEMENT!
  // Right. If you hate 'em, see the FAQ below.
}

Components

Dumb Components

// The dumb component receives everything using props:
import React, { PropTypes } from 'react';

export default class Counter {
  static propTypes = {
    increment: PropTypes.func.isRequired,
    decrement: PropTypes.func.isRequired,
    counter: PropTypes.number.isRequired
  };

  render() {
    const { increment, decrement, counter } = this.props;
    return (
      <p>
        Clicked: {counter} times
        {' '}
        <button onClick={increment}>+</button>
        {' '}
        <button onClick={decrement}>-</button>
      </p>
    );
  }
}

Smart Components

// The smart component may observe stores using `<Connector />`,
// and bind actions to the dispatcher with `bindActionCreators`.

import React from 'react';
import { bindActionCreators } from 'redux';
import { Connector } from 'redux/react';
import Counter from '../components/Counter';
import * as CounterActions from '../actions/CounterActions';

// You can optionally specify `select` for finer-grained subscriptions
// and retrieval. Only when the return value is shallowly different,
// will the child component be updated.
function select(state) {
  return { counter: state.counter };
}

export default class CounterApp {
  render() {
    return (
      <Connector select={select}>
        {({ counter, dispatch }) =>
          /* Yes this is child as a function. */
          <Counter counter={counter}
                   {...bindActionCreators(CounterActions, dispatch)} />
        }
      </Connector>
    );
  }
}

Decorators

The @connect decorator lets you create smart components less verbosely:

import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'redux/react';
import Counter from '../components/Counter';
import * as CounterActions from '../actions/CounterActions';

@connect(state => ({
  counter: state.counter
}))
export default class CounterApp {
  render() {
    const { counter, dispatch } = this.props;
    // Instead of `bindActionCreators`, you may also pass `dispatch` as a prop
    // to your component and call `dispatch(CounterActions.increment())`
    return (
      <Counter counter={counter}
               {...bindActionCreators(CounterActions, dispatch)} />
    );
  }
}

React Native

To use Redux with React Native, just replace imports from redux/react with redux/react-native:

import { bindActionCreators } from 'redux';
import { Provider, Connector } from 'redux/react-native';

Initializing Redux

The simplest way to initialize a Redux instance is to give it an object whose values are your Store functions, and whose keys are their names. You may import * from the file with all your Store definitions to obtain such an object:

import { createRedux } from 'redux';
import { Provider } from 'redux/react';
import * as stores from '../stores/index';

const redux = createRedux(stores);

Then pass redux as a prop to <Provider> component in the root component of your app, and you're all set:

export default class App {
  render() {
    return (
      <Provider redux={redux}>
        {() =>
          <CounterApp />
        }
      </Provider>
    );
  }
}

Running the same code on client and server

The redux instance returned by createRedux also has the dispatch(action), subscribe() and getState() methods that you may call outside the React components.

You may optionally specify the initial state as the second argument to createRedux. This is useful for hydrating the state you received from running Redux on the server:

// server
const redux = createRedux(stores);
redux.dispatch(MyActionCreators.doSomething()); // fire action creators to fill the state
const state = redux.getState(); // somehow pass this state to the client

// client
const initialState = window.STATE_FROM_SERVER;
const redux = createRedux(stores, initialState);

Additional customization

There is also a longer way to do the same thing, if you need additional customization.

This:

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

const redux = createRedux(stores);

is in fact a shortcut for this:

import { createRedux, createDispatcher, composeStores } from 'redux';
import thunkMiddleware from 'redux/lib/middleware/thunk';
import * as stores from '../stores/index';

// Compose all your Stores into a single Store function with `composeStores`:
const store = composeStores(stores);

// Create a Dispatcher function for your composite Store:
const dispatcher = createDispatcher(
  store,
  getState => [thunkMiddleware(getState)] // Pass the default middleware
);

// Create a Redux instance using the dispatcher function:
const redux = createRedux(dispatcher);

Why would you want to write it longer? Maybe you're an advanced user and want to provide a custom Dispatcher function, or maybe you have a different idea of how to compose your Stores (or you're satisfied with a single Store). Redux lets you do all of this.

createDispatcher() also gives you the ability to specify middleware -- for example, to add support for promises. Learn more about how to create and use middleware in Redux.

When in doubt, use the shorter option!

FAQ

How does hot reloading work?

Can I use this in production?

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

But there are switch statements!

(state, action) => state is as simple as a Store can get. You are free to implement your own createStore:

export default function createStore(initialState, handlers) {
  return (state = initialState, action) =>
    handlers[action.type] ?
      handlers[action.type](state, action) :
      state;
}

and use it for your Stores:

export default createStore(0, {
  [INCREMENT_COUNTER]: x => x + 1,
  [DECREMENT_COUNTER]: x => x - 1
});

It's all just functions. Fancy stuff like generating stores from handler maps, or generating action creator constants, should be in userland. Redux has no opinion on how you do this in your project.

What about waitFor?

I wrote a lot of vanilla Flux code and my only use case for it was to avoid emitting a change before a related Store consumes the action. This doesn't matter in Redux because the change is only emitted after all Stores have consumed the action.

If several of your Stores want to read data from each other and depend on each other, it's a sign that they should've been a single Store instead. See this discussion on how waitFor can be replaced by the composition of stateless Stores.

My views aren't updating!

Redux makes a hard assumption that you never mutate the state passed to you. It's easy! For example, instead of

function (state, action) {
  state.isAuthenticated = true;
  state.email = action.email;
  return state;
}

you should write

function (state, action) {
  return {
    ...state,
    isAuthenticated: true,
    email: action.email
  };
}

Read more about the spread properties ES7 proposal.

Inspiration and Thanks

Special thanks go to Jamie Paton for handing over the redux NPM package name.

About

An experiment in fully hot-reloadable Flux

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 99.3%
  • Other 0.7%