diff --git a/.bookignore b/.bookignore new file mode 100644 index 00000000..f7b8f0ca --- /dev/null +++ b/.bookignore @@ -0,0 +1,4 @@ +src/ +build/ +package.json +webpack* diff --git a/.gitignore b/.gitignore index 2403f02f..49b9e600 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ es lib dist .idea + +# docs generated files +_book diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..12ff5902 --- /dev/null +++ b/.npmignore @@ -0,0 +1,5 @@ +# doc folders +docs +_book +.bookignore +book.json diff --git a/CNAME b/CNAME new file mode 100644 index 00000000..64f4f907 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +redux-actions.js.org diff --git a/README.md b/README.md index b3b06e8b..eaa25121 100644 --- a/README.md +++ b/README.md @@ -6,258 +6,38 @@ [Flux Standard Action](https://github.com/acdlite/flux-standard-action) utilities for Redux. -## Installation - -```bash -npm install --save redux-actions -``` - -The [npm](https://www.npmjs.com) package provides a [CommonJS](http://webpack.github.io/docs/commonjs.html) build for use in Node.js, and with bundlers like [Webpack](http://webpack.github.io/) and [Browserify](http://browserify.org/). It also includes an [ES modules](http://jsmodules.io/) build that works well with [Rollup](http://rollupjs.org/) and [Webpack2](https://webpack.js.org)'s tree-shaking. - -If you don’t use [npm](https://www.npmjs.com), you may grab the latest [UMD](https://unpkg.com/redux-actions@latest/dist) build from [unpkg](https://unpkg.com) (either a [development](https://unpkg.com/redux-actions@latest/dist/redux-actions.js) or a [production](https://unpkg.com/redux-actions@latest/dist/redux-actions.min.js) build). The UMD build exports a global called `window.ReduxActions` if you add it to your page via a ` + + + + + +
+ + + +``` + +Now that we are ready to write some JS let us create our default state for our counter. + +```js +const defaultState = { counter: 0 }; +``` + +Now if we want to see our default state we need to render it. +Lets get a reference to our main content and render our default state into the html. + +```js +const content = document.getElementById('content'); + +const render = () => { + content.innerHTML = defaultState.counter; +}; +render(); +``` + +With our default state and renderer in place we can start to use our libraries. `redux` and `redux-actions` can be found via the globals `window.Redux` and `window.ReduxActions`. Okay enough setup lets start to make something with `redux`! + +We are going to want a store for our defaultState. We can create one from `redux` using `createStore`. + +```js +const { createStore } = window.Redux; +``` + +We are going to want to create our first action and handle that action. + +```js +const { + createAction, + handleAction +} = window.ReduxActions; +``` + +Next lets create our first action, 'increment', using `createAction`. + +```js +const increment = createAction('INCREMENT'); +``` + +Next we are going to handle that action with `handleAction`. We can provide it our `increment` action to let it know which action to handle. A method to handle our state transformation, and the default state. + +```js +const reducer = handleAction(increment, (state, action) => ({ + counter: state.counter + 1 +}), defaultState); +``` + +`handleAction` produced a reducer for our `redux` store. Now that we have a reducer we can create a store. + +```js +const store = createStore(reducer, defaultState); +``` + +Now that we have a `store`, we can rewrite our `render` method to use it instead of the `defaultState`. We also want to `subscribe` our `render` to any changes the `store` might have for us. + +```js +const render = () => { + content.innerHTML = store.getState().counter; +}; + +store.subscribe(render); +``` + +We are ready to `dispatch` an action. Lets create an event listener for our increment button that will dispatch our `increment` action creator when clicked. + +```js +document.getElementById('increment').addEventListener('click', () => { + store.dispatch(increment()); +}); +``` + +If you try to click the increment button you should see the value is now going up by one on each click. + +We have one button working, so why don't we try to get the second one working by creating a new action for decrement. + +```js +const decrement = createAction('DECREMENT'); +``` + +Instead of using `handleAction` like we did for `increment`, we can replace it with our other tool `handleActions` which will let us handle both `increment` and `decrement` actions. + +```js +const { + createAction, + handleActions +} = window.ReduxActions; + +const reducer = handleActions({ + [increment](state) { + return { counter: state.counter + 1 } + }, + [decrement](state) { + return { counter: state.counter - 1 } + } +}, defaultState); +``` + +Now when we add a handler for dispatching our `decrement` action we can see both `increment` and `decrement` buttons now function appropriately. + +```js +document.getElementById('decrement').addEventListener('click', () => { + store.dispatch(decrement()); +}); +``` + +You might be thinking at this point we are all done. We have both buttons hooked up, and we can call it a day. Yet we have much optimizing to do. `redux-actions` has other tools we have not yet taken advantage of. So lets investigate how we can change the code to use the remaining tools and make the code less verbose. + +We have declarations for both `increment` and `decrement` action creators. We can modify these lines from using `createAction` to using `createActions` like so. + +```js +const { + createActions, + handleActions +} = window.ReduxActions; + +const { increment, decrement } = createActions('INCREMENT', 'DECREMENT'); +``` + +We can still do better though. What if we want an action like `'INCREMENT_FIVE'`? We would want to be able to create variations of our existing actions easily. We can abstract our logic in the reducer to our actions, making new permutations of existing actions easy to create. + +```js +const { increment, decrement } = createActions({ + 'INCREMENT': amount => ({ amount: 1 }), + 'DECREMENT': amount => ({ amount: -1 }) +}); + +const reducer = handleActions({ + [increment](state, { payload: { amount } }) { + return { counter: state.counter + amount } + }, + [decrement](state, { payload: { amount } }) { + return { counter: state.counter + amount } + } +}, defaultState); +``` + +Now that we have moved our logic, our `reducers` are looking identical. If only we could combine them somehow. Well we can! `combineActions` can be used to reduce multiple distinct actions with the same reducer. + +```js +const { + createActions, + handleActions, + combineActions +} = window.ReduxActions; + +const reducer = handleActions({ + [combineActions(increment, decrement)](state, { payload: { amount } }) { + return { ...state, counter: state.counter + amount }; + } +}, defaultState); +``` + +We have finally used all of the tools that `redux-actions` has to offer. Concluding our [vanilla tutorial](https://www.webpackbin.com/bins/-KntJIfbsxVzsD98UEWF). This doesn't mean you don't have more to learn though. Much more can be accomplished using these tools in many ways, just head on over to the [API Reference](../api) to begin exploring what else `redux-actions` can do for you. diff --git a/docs/middleware/README.md b/docs/middleware/README.md new file mode 100644 index 00000000..001b567c --- /dev/null +++ b/docs/middleware/README.md @@ -0,0 +1,25 @@ +# Usage with Middleware + +redux-actions is handy all by itself, however, its real power comes when you combine it with middleware. + +The identity form of `createAction` is a great way to create a single action creator that handles multiple payload types. For example, using [redux-promise](https://github.com/acdlite/redux-promise) and [redux-rx](https://github.com/acdlite/redux-rx): + +```js +const addTodo = createAction('ADD_TODO'); + +// A single reducer... +handleAction('ADD_TODO', (state = { todos: [] }, action) => ({ + ...state, + todos: [...state.todos, action.payload] +})); + +// ...that works with all of these forms: +// (Don't forget to use `bindActionCreators()` or equivalent. +// I've left that bit out) +addTodo('Use Redux') +addTodo(Promise.resolve('Weep with joy')); +addTodo(Observable.of( + 'Learn about middleware', + 'Learn about higher-order stores' +)).subscribe(); +``` \ No newline at end of file diff --git a/package.json b/package.json index 0d35733a..8799c761 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,12 @@ "lint:watch": "npm run lint -- --watch", "prepublish": "npm run lint && npm run test && npm run build", "test": "mocha --compilers js:babel-register src/**/*-test.js", - "test:watch": "npm run test -- --watch src/**/*-test.js" + "test:watch": "npm run test -- --watch src/**/*-test.js", + "docs:clean": "rimraf _book", + "docs:prepare": "gitbook install", + "docs:build": "npm run docs:prepare && gitbook build -g acdlite/redux-actions", + "docs:watch": "npm run docs:prepare && gitbook serve", + "docs:publish": "npm run docs:clean && npm run docs:build && cp CNAME _book && cd _book && git init && git commit --allow-empty -m 'update book' && git checkout -b gh-pages && touch .nojekyll && git add . && git commit -am 'update book' && git push git@github.com:acdlite/redux-actions gh-pages --force" }, "files": [ "es", @@ -55,6 +60,7 @@ "eslint-plugin-import": "^1.5.0", "eslint-watch": "^2.1.13", "flux-standard-action": "^1.0.0", + "gitbook-cli": "^2.3.0", "mocha": "^2.2.5", "rimraf": "^2.5.3", "webpack": "^1.13.1"