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

goatslacker/redux

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

76 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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.
  • Keep Flux lingo. No cursors or observables in core.
  • Have I mentioned hot reloading yet?

Demo

gif

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;
  }
  
  // BUT THAT'S A SWITCH STATEMENT!
  // Right. If you hate 'em, see the FAQ below.
}

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

Components

Dumb Components

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

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

  render() {
    const { counter } = this.props;
    return (
      <p>
        Clicked: {counter} times
      </p>
    );
  }
}

Smart Components

// The smart component may inject actions
// and observe stores using <Container />:

import React, { Component } from 'react';
import { Root, Container } from 'redux';
import { increment, decrement } from './actions/CounterActions';
import counterStore from './stores/counterStore';
import Counter from './Counter';

export default class CounterContainer {
  render() {
    // stores must be an array.
    // actions must be a string -> function map.
    // props passed to children will combine these actions and state.
    return (
      <Container stores={[counterStore]}
                 actions={{ increment, decrement }}>
        {/* Yes this is a function as a child. Bear with me. */}
        {props => <Counter {...props} />}
      </Container>
    );
  }
}

Decorators

Don't want to separate dumb and smart components just yet? Use the decorators!
They work exactly the same as the container components, but are lowercase:

import React, { PropTypes } from 'react';
import { increment, decrement } from './actions/CounterActions';
import { container } from 'redux';
import counterStore from './stores/counterStore';

@container({
  actions: { increment, decrement },
  stores: [counterStore]
})
export default class Counter {
  static propTypes = {
    increment: PropTypes.func.isRequired,
    decrement: PropTypes.func.isRequired,
    counter: PropTypes.number.isRequired
  };

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

The root component

import React from 'react';
import { root } from 'redux';
import * as stores from './stores/index';

// Let it know about all the stores
@root(stores)
export default class App {
  /* ... */
}

FAQ

How does hot reloading work?

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.

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(initialState, {
  [INCREMENT_COUNTER]: increment,
  [DECREMENT_COUNTER]: decrement
});

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 avoiding emitting a change before a related Store consumes the action. In Redux this doesn't matter 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 they should'be been a single Store instead.

About

An experiment in fully hot-reloadable Flux

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 98.3%
  • HTML 1.2%
  • Shell 0.5%