Skip to content

Commit

Permalink
Breaking API changes for 1.0
Browse files Browse the repository at this point in the history
Naming:

* “Stateless Stores” are now called reducers. (#137 (comment))
* The “Redux instance” is now called “The Store”. (#137 (comment))
* The dispatcher is removed completely. (#166 (comment))

API changes:

* <s>`composeStores`</s> is now `composeReducers`.
* <s>`createDispatcher`</s> is gone.
* <s>`createRedux`</s> is now `createStore`.
* `<Provider>` now accepts `store` prop instead of <s>`redux`</s>.
* The new `createStore` signature is `createStore(reducer: Function | Object, initialState: any, middlewares: Array | ({ getState, dispatch }) => Array)`.
* If the first argument to `createStore` is an object, `composeReducers` is automatically applied to it.
* The “smart” middleware signature changed. It now accepts an object instead of a single `getState` function. The `dispatch` function lets you “recurse” the middleware chain and is useful for async: #113 (comment).

Correctness changes:

* The `dispatch` provided by the default thunk middleware now walks the whole middleware chain.
* It is enforced now that raw Actions at the end of the middleware chain have to be plain objects.
* Nested dispatches are now handled gracefully. (#110)

Internal changes:

* The object in React context is renamed from <s>`redux`</s> to `store`.
* Some tests are rewritten for clarity, focus and edge cases.
* Redux in examples is now aliased to the source code for easier work on master.
  • Loading branch information
gaearon committed Jun 30, 2015
1 parent d3a31b2 commit e426039
Show file tree
Hide file tree
Showing 44 changed files with 816 additions and 475 deletions.
8 changes: 4 additions & 4 deletions examples/counter/containers/App.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React from 'react';
import CounterApp from './CounterApp';
import { createRedux } from 'redux';
import { createStore } from 'redux/index';
import { Provider } from 'redux/react';
import * as stores from '../stores';
import * as reducers from '../reducers';

const redux = createRedux(stores);
const store = createStore(reducers);

export default class App {
render() {
return (
<Provider redux={redux}>
<Provider store={store}>
{() => <CounterApp />}
</Provider>
);
Expand Down
2 changes: 1 addition & 1 deletion examples/counter/containers/CounterApp.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { bindActionCreators } from 'redux';
import { bindActionCreators } from 'redux/index';
import { connect } from 'redux/react';
import Counter from '../components/Counter';
import * as CounterActions from '../actions/CounterActions';
Expand Down
File renamed without changes.
File renamed without changes.
3 changes: 3 additions & 0 deletions examples/counter/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ module.exports = {
new webpack.NoErrorsPlugin()
],
resolve: {
alias: {
'redux': path.join(__dirname, '../../src')
},
extensions: ['', '.js']
},
module: {
Expand Down
11 changes: 6 additions & 5 deletions examples/todomvc/containers/App.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import React from 'react';
import TodoApp from './TodoApp';
import { createRedux } from 'redux';
import { createStore, composeReducers } from 'redux/index';
import { Provider } from 'redux/react';
import * as stores from '../stores';
import * as reducers from '../reducers';

const redux = createRedux(stores);
const reducer = composeReducers(reducers);
const store = createStore(reducer);

export default class App {
render() {
return (
<Provider redux={redux}>
{() => <TodoApp />}
<Provider store={store}>
{() => <TodoApp /> }
</Provider>
);
}
Expand Down
2 changes: 1 addition & 1 deletion examples/todomvc/containers/TodoApp.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { bindActionCreators } from 'redux';
import { bindActionCreators } from 'redux/index';
import { Connector } from 'redux/react';
import Header from '../components/Header';
import MainSection from '../components/MainSection';
Expand Down
File renamed without changes.
File renamed without changes.
3 changes: 3 additions & 0 deletions examples/todomvc/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ module.exports = {
new webpack.NoErrorsPlugin()
],
resolve: {
alias: {
'redux': path.join(__dirname, '../../src')
},
extensions: ['', '.js']
},
module: {
Expand Down
53 changes: 0 additions & 53 deletions src/Redux.js

This file was deleted.

50 changes: 50 additions & 0 deletions src/Store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import invariant from 'invariant';
import isPlainObject from './utils/isPlainObject';

export default class Store {
constructor(reducer, initialState) {
invariant(
typeof reducer === 'function',
'Expected the reducer to be a function.'
);

this.state = initialState;
this.listeners = [];
this.replaceReducer(reducer);
}

getReducer() {
return this.reducer;
}

replaceReducer(nextReducer) {
this.reducer = nextReducer;
this.dispatch({ type: '@@INIT' });
}

dispatch(action) {
invariant(
isPlainObject(action),
'Actions must be plain objects. Use custom middleware for async actions.'
);

This comment has been minimized.

Copy link
@acdlite

acdlite Jul 4, 2015

Collaborator

This invariant is always true because createStore() does a type check before passing it to Store, and Store is not part of the public API. Perhaps instead there should be an invariant inside createStore() that checks if reducer it is either a function or a plain object?

const { reducer } = this;
this.state = reducer(this.state, action);
this.listeners.forEach(listener => listener());
return action;
}

getState() {
return this.state;
}

subscribe(listener) {
const { listeners } = this;
listeners.push(listener);

return function unsubscribe() {
const index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
}
}
12 changes: 6 additions & 6 deletions src/components/createConnector.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import createReduxShape from '../utils/createReduxShape';
import createStoreShape from '../utils/createStoreShape';
import identity from '../utils/identity';
import shallowEqual from '../utils/shallowEqual';
import isPlainObject from '../utils/isPlainObject';
import invariant from 'invariant';

export default function createConnector(React) {
const { Component, PropTypes } = React;
const storeShape = createStoreShape(PropTypes);

return class Connector extends Component {
static contextTypes = {
redux: createReduxShape(PropTypes).isRequired
store: storeShape.isRequired
};

static propTypes = {
Expand Down Expand Up @@ -38,12 +39,11 @@ export default function createConnector(React) {

constructor(props, context) {
super(props, context);

this.state = this.selectState(props, context);
}

componentDidMount() {
this.unsubscribe = this.context.redux.subscribe(::this.handleChange);
this.unsubscribe = this.context.store.subscribe(::this.handleChange);
}

componentWillReceiveProps(nextProps) {
Expand All @@ -63,7 +63,7 @@ export default function createConnector(React) {
}

selectState(props, context) {
const state = context.redux.getState();
const state = context.store.getState();
const slice = props.select(state);

invariant(
Expand All @@ -78,7 +78,7 @@ export default function createConnector(React) {
render() {
const { children } = this.props;
const { slice } = this.state;
const { redux: { dispatch } } = this.context;
const { store: { dispatch } } = this.context;

return children({ dispatch, ...slice });
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/createProvideDecorator.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import getDisplayName from '../utils/getDisplayName';
export default function createProvideDecorator(React, Provider) {
const { Component } = React;

return function provide(redux) {
return function provide(store) {
return DecoratedComponent => class ProviderDecorator extends Component {
static displayName = `Provider(${getDisplayName(DecoratedComponent)})`;
static DecoratedComponent = DecoratedComponent;

render() {
return (
<Provider redux={redux}>
<Provider store={store}>
{() => <DecoratedComponent {...this.props} />}
</Provider>
);
Expand Down
28 changes: 13 additions & 15 deletions src/components/createProvider.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
import createReduxShape from '../utils/createReduxShape';
import createStoreShape from '../utils/createStoreShape';

export default function createProvider(React) {
const { Component, PropTypes } = React;

const reduxShapeIsRequired = createReduxShape(PropTypes).isRequired;
const storeShape = createStoreShape(PropTypes);

return class Provider extends Component {
static propTypes = {
redux: reduxShapeIsRequired,
children: PropTypes.func.isRequired
static childContextTypes = {
store: storeShape.isRequired
};

static childContextTypes = {
redux: reduxShapeIsRequired
static propTypes = {
children: PropTypes.func.isRequired
};

getChildContext() {
return { redux: this.state.redux };
return { store: this.state.store };
}

constructor(props, context) {
super(props, context);
this.state = { redux: props.redux };
this.state = { store: props.store };
}

componentWillReceiveProps(nextProps) {
const { redux } = this.state;
const { redux: nextRedux } = nextProps;
const { store } = this.state;
const { store: nextStore } = nextProps;

if (redux !== nextRedux) {
const nextDispatcher = nextRedux.getDispatcher();
redux.replaceDispatcher(nextDispatcher);
if (store !== nextStore) {
const nextReducer = nextStore.getReducer();
store.replaceReducer(nextReducer);
}
}

Expand Down
26 changes: 0 additions & 26 deletions src/createDispatcher.js

This file was deleted.

13 changes: 0 additions & 13 deletions src/createRedux.js

This file was deleted.

45 changes: 45 additions & 0 deletions src/createStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Store from './Store';
import composeReducers from './utils/composeReducers';
import composeMiddleware from './utils/composeMiddleware';
import thunkMiddleware from './middleware/thunk';

const defaultMiddlewares = ({ dispatch, getState }) => [
thunkMiddleware({ dispatch, getState })
];

export default function createStore(
reducer,
initialState,
middlewares = defaultMiddlewares
) {
const finalReducer = typeof reducer === 'function' ?
reducer :
composeReducers(reducer);

const store = new Store(finalReducer, initialState);
const getState = ::store.getState;

const rawDispatch = ::store.dispatch;
let cookedDispatch = null;

function dispatch(action) {
return cookedDispatch(action);
}

const finalMiddlewares = typeof middlewares === 'function' ?
middlewares({ dispatch, getState }) :
middlewares;

cookedDispatch = composeMiddleware(
...finalMiddlewares,
rawDispatch
);

return {
dispatch: cookedDispatch,
subscribe: ::store.subscribe,
getState: ::store.getState,
getReducer: ::store.getReducer,
replaceReducer: ::store.replaceReducer
};
}
Loading

0 comments on commit e426039

Please sign in to comment.