diff --git a/README.md b/README.md index 11be17b5b6..8d3d20e862 100644 --- a/README.md +++ b/README.md @@ -7,65 +7,11 @@ redux Atomic Flux with hot reloading. -**The API is likely to change a few times before we reach 1.0.**
-**Its [surface area](http://www.youtube.com/watch?v=4anAwXYqLG8) is minimal so you can try it in production and report any issues.** - -**You can track the [new docs](https://github.com/gaearon/redux/pull/140) and the [1.0 API and terminology changes](https://github.com/gaearon/redux/pull/195).** - - -# Table of Contents - -- [Why another Flux framework?](#why-another-flux-framework) - - [Philosophy & Design Goals](#philosophy--design-goals) -- [The Talk](#the-talk) -- [Demo](#demo) -- [Examples](#examples) - - [Simple Examples](#simple-examples) - - [Async and Universal Examples with Routing](#async-and-universal-examples-with-routing) -- [What does it look like?](#what-does-it-look-like) - - [Actions](#actions) - - [Stores](#stores) - - [Components](#components) - - [Dumb Components](#dumb-components) - - [Smart Components](#smart-components) - - [Decorators](#decorators) - - [React Native](#react-native) - - [Initializing Redux](#initializing-redux) - - [Running the same code on client and server](#running-the-same-code-on-client-and-server) - - [Additional customization](#additional-customization) -- [FAQ](#faq) - - [How does hot reloading work?](#how-does-hot-reloading-work) - - [Can I use this in production?](#can-i-use-this-in-production) - - [How do I do async?](#how-do-i-do-async) - - [But there are switch statements!](#but-there-are-switch-statements) - - [What about `waitFor`?](#what-about-waitfor) - - [My views aren't updating!](#my-views-arent-updating) - - [How do Stores, Actions and Components interact?](#how-do-stores-actions-and-components-interact) -- [Discussion](#discussion) -- [Inspiration and Thanks](#inspiration-and-thanks) ## Why another Flux framework? Read **[The Evolution of Flux Frameworks](https://medium.com/@dan_abramov/the-evolution-of-flux-frameworks-6c16ad26bb31)** for some context. -### Philosophy & Design Goals - -* You shouldn't need a book on functional programming to use Redux. -* Everything (Stores, Action Creators, configuration) is hot reloadable. -* Preserves the benefits of Flux, but adds other nice properties thanks to its functional nature. -* Prevents some of the anti-patterns common in Flux code. -* Works great in [universal (aka “isomorphic”)](https://medium.com/@mjackson/universal-javascript-4761051b7ae9) apps because it doesn't use singletons and the data can be rehydrated. -* Doesn't care how you store your data: you may use JS objects, arrays, ImmutableJS, etc. -* Under the hood, it keeps all your data in a tree, but you don't need to think about it. -* Lets you efficiently subscribe to finer-grained updates than individual Stores. -* Provides hooks for powerful devtools (e.g. time travel, record/replay) to be implementable without user buy-in. -* Provides extension points so it's easy to [support promises](https://github.com/gaearon/redux/issues/99#issuecomment-112212639) or [generate constants](https://gist.github.com/skevy/8a4ffc3cfdaf5fd68739) outside the core. -* No wrapper calls in your stores and actions. Your stuff is your stuff. -* It's super easy to test things in isolation without mocks. -* You can use “flat” Stores, or [compose and reuse Stores](https://gist.github.com/gaearon/d77ca812015c0356654f) just like you compose Components. -* The API surface area is minimal. -* Have I mentioned hot reloading yet? - ## The Talk Redux was demoed together with **[React Hot Loader](https://github.com/gaearon/react-hot-loader)** at React Europe. @@ -75,405 +21,6 @@ Watch **[Dan Abramov's talk on Hot Reloading with Time Travel](https://www.youtu -## Examples - -### Simple Examples - -Redux is distributed with a Counter and a TodoMVC example in its source code. - -First, clone the repo: - -``` -git clone https://github.com/gaearon/redux.git -cd redux -``` - -Run the Counter example: - -``` -cd redux/examples/counter -npm install -npm start -``` - -Run the TodoMVC example: - -``` -cd ../todomvc -npm install -npm start -``` - -### Async and Universal Examples with Routing - -These async and [universal (aka “isomorphic”)](https://medium.com/@mjackson/universal-javascript-4761051b7ae9) examples using React Router should help you get started: - -* [redux-react-router-async-example](https://github.com/emmenko/redux-react-router-async-example): Work in progress. Semi-official. Only the client side. Uses React Router. -* [react-redux-universal-hot-example](https://github.com/erikras/react-redux-universal-hot-example): Universal. Uses React Router. -* [redux-example](https://github.com/quangbuule/redux-example): Universal. Uses Immutable, React Router. -* [isomorphic-counter-example](https://github.com/khtdr/redux-react-koa-isomorphic-counter-example): Universal. A bare-bone implentation of the [counter example app](https://github.com/gaearon/redux/tree/master/examples/counter). Uses promises-middleware to interact with API via Koa on the server. - -Don’t be shy, add your own! - -## What does it look like? - -### Actions - -```js -// 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, getState) => { - const { counter } = getState(); - - if (counter % 2 === 0) { - return; - } - - dispatch(increment()); - }; -} -``` - -### Stores -```js -// ... 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 - -```js -// 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 ( -

- Clicked: {counter} times - {' '} - - {' '} - -

- ); - } -} -``` - -#### Smart Components - -```js -// The smart component may observe stores using ``, -// 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 ( - - {({ counter, dispatch }) => - /* Yes this is child as a function. */ - - } - - ); - } -} -``` - -#### Decorators - -The `@connect` decorator lets you create smart components less verbosely: - -```js -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 ( - - ); - } -} -``` - -### React Native - -To use Redux with React Native, just replace imports from `redux/react` with `redux/react-native`: - -```js -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: - -```js -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 `` component in the root component of your app, and you're all set: - -```js -export default class App { - render() { - return ( - - {() => - - } - - ); - } -} -``` - -### 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: - -```js -// 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: - -```js -import { createRedux } from 'redux'; -import * as stores from '../stores/index'; - -const redux = createRedux(stores); -``` - -is in fact a shortcut for this: - -```js -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](https://github.com/gaearon/redux/blob/master/docs/middleware.md) about how to create and use middleware in Redux. - -When in doubt, use the shorter option! - -## FAQ - -### How does hot reloading work? - -* http://webpack.github.io/docs/hot-module-replacement.html -* http://gaearon.github.io/react-hot-loader/ -* Literally that's it. Redux is fully driven by component props, so it works on top of React Hot Loader. - -### Can I use this in production? - -Yep. People already do that although I warned them! The API surface is minimal so migrating to 1.0 API when it comes out won't be difficult. Let us know about any issues. - -### How do I do async? - -There's already a built-in way of doing async action creators: - -```js -// 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); - }; -} -``` - -It's also easy to implement support for returning Promises or Observables with a custom middleware. [See an example of a custom Promise middleware.](https://github.com/gaearon/redux/issues/99#issuecomment-112212639) - -### But there are switch statements! - -`(state, action) => state` is as simple as a Store can get. You are free to implement your own `createStore`: - -```js -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: - -```js -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. - -See also [this gist](https://gist.github.com/skevy/8a4ffc3cfdaf5fd68739) for an example implementation of action constant generation. - -### 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.](https://gist.github.com/gaearon/d77ca812015c0356654f) - -### 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 - -```js -function (state, action) { - state.isAuthenticated = true; - state.email = action.email; - return state; -} -``` - -you should write - -```js -function (state, action) { - return { - ...state, - isAuthenticated: true, - email: action.email - }; -} -``` - -[Read more](https://github.com/sebmarkbage/ecmascript-rest-spread) about the spread properties ES7 proposal. - -### How do Stores, Actions and Components interact? - -Action creators are just pure functions so they don't interact with anything. Components need to call `dispatch(action)` (or use `bindActionCreators` that wraps it) to dispatch an action *returned* by the action creator. - -Stores are just pure functions too so they don't need to be “registered” in the traditional sense, and you can't subscribe to them directly. They're just descriptions of how data transforms. So in that sense they don't “interact” with anything either, they just exist, and are used by the dispatcher for computation of the next state. - -Now, the dispatcher is more interesting. You pass all the Stores to it, and it composes them into a single Store function that it uses for computation. The dispatcher is also a pure function, and it is passed as configuration to `createRedux`, the only stateful thing in Redux. By default, the default dispatcher is used, so if you call `createRedux(stores)`, it is created implicitly. - -To sum it up: there is a Redux instance at the root of your app. It binds everything together. It accepts a dispatcher (which itself accepts Stores), it holds the state, and it knows how to turn actions into state updates. Everything else (components, for example) subscribes to the Redux instance. If something wants to dispatch an action, they need to do it on the Redux instance. `Connector` is a handy shortcut for subscribing to a slice of the Redux instance's state and injecting `dispatch` into your components, but you don't have to use it. - -There is no other “interaction” in Redux. ## Discussion diff --git a/docs/design-goals.md b/docs/design-goals.md new file mode 100644 index 0000000000..f583ff0a88 --- /dev/null +++ b/docs/design-goals.md @@ -0,0 +1,17 @@ +### Philosophy & Design Goals + +* You shouldn't need a book on functional programming to use Redux. +* Everything (Stores, Action Creators, configuration) is hot reloadable. +* Preserves the benefits of Flux, but adds other nice properties thanks to its functional nature. +* Prevents some of the anti-patterns common in Flux code. +* Works great in [universal (aka “isomorphic”)](https://medium.com/@mjackson/universal-javascript-4761051b7ae9) apps because it doesn't use singletons and the data can be rehydrated. +* Doesn't care how you store your data: you may use JS objects, arrays, ImmutableJS, etc. +* Under the hood, it keeps all your data in a tree, but you don't need to think about it. +* Lets you efficiently subscribe to finer-grained updates than individual Stores. +* Provides hooks for powerful devtools (e.g. time travel, record/replay) to be implementable without user buy-in. +* Provides extension points so it's easy to [support promises](https://github.com/gaearon/redux/issues/99#issuecomment-112212639) or [generate constants](https://gist.github.com/skevy/8a4ffc3cfdaf5fd68739) outside the core. +* No wrapper calls in your stores and actions. Your stuff is your stuff. +* It's super easy to test things in isolation without mocks. +* You can use “flat” Stores, or [compose and reuse Stores](https://gist.github.com/gaearon/d77ca812015c0356654f) just like you compose Components. +* The API surface area is minimal. +* Have I mentioned hot reloading yet? diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 0000000000..4a619718db --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,39 @@ +## Examples + +### Simple Examples + +Redux is distributed with a Counter and a TodoMVC example in its source code. + +First, clone the repo: + +``` +git clone https://github.com/gaearon/redux.git +cd redux +``` + +Run the Counter example: + +``` +cd redux/examples/counter +npm install +npm start +``` + +Run the TodoMVC example: + +``` +cd ../todomvc +npm install +npm start +``` + +### Async and Universal Examples with Routing + +These async and [universal (aka “isomorphic”)](https://medium.com/@mjackson/universal-javascript-4761051b7ae9) examples using React Router should help you get started: + +* [redux-react-router-async-example](https://github.com/emmenko/redux-react-router-async-example): Work in progress. Semi-official. Only the client side. Uses React Router. +* [react-redux-universal-hot-example](https://github.com/erikras/react-redux-universal-hot-example): Universal. Uses React Router. +* [redux-example](https://github.com/quangbuule/redux-example): Universal. Uses Immutable, React Router. +* [isomorphic-counter-example](https://github.com/khtdr/redux-react-koa-isomorphic-counter-example): Universal. A bare-bone implentation of the [counter example app](https://github.com/gaearon/redux/tree/master/examples/counter). Uses promises-middleware to interact with API via Koa on the server. + +Don’t be shy, add your own! diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000000..6bfc1dd778 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,101 @@ +## FAQ + +### How does hot reloading work? + +* http://webpack.github.io/docs/hot-module-replacement.html +* http://gaearon.github.io/react-hot-loader/ +* Literally that's it. Redux is fully driven by component props, so it works on top of React Hot Loader. + +### Can I use this in production? + +Yep. People already do that although I warned them! The API surface is minimal so migrating to 1.0 API when it comes out won't be difficult. Let us know about any issues. + +### How do I do async? + +There's already a built-in way of doing async action creators: + +```js +// 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); + }; +} +``` + +It's also easy to implement support for returning Promises or Observables with a custom middleware. [See an example of a custom Promise middleware.](https://github.com/gaearon/redux/issues/99#issuecomment-112212639) + +### But there are switch statements! + +`(state, action) => state` is as simple as a Store can get. You are free to implement your own `createStore`: + +```js +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: + +```js +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. + +See also [this gist](https://gist.github.com/skevy/8a4ffc3cfdaf5fd68739) for an example implementation of action constant generation. + +### 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.](https://gist.github.com/gaearon/d77ca812015c0356654f) + +### 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 + +```js +function (state, action) { + state.isAuthenticated = true; + state.email = action.email; + return state; +} +``` + +you should write + +```js +function (state, action) { + return { + ...state, + isAuthenticated: true, + email: action.email + }; +} +``` + +[Read more](https://github.com/sebmarkbage/ecmascript-rest-spread) about the spread properties ES7 proposal. + +### How do Stores, Actions and Components interact? + +Action creators are just pure functions so they don't interact with anything. Components need to call `dispatch(action)` (or use `bindActionCreators` that wraps it) to dispatch an action *returned* by the action creator. + +Stores are just pure functions too so they don't need to be “registered” in the traditional sense, and you can't subscribe to them directly. They're just descriptions of how data transforms. So in that sense they don't “interact” with anything either, they just exist, and are used by the dispatcher for computation of the next state. + +Now, the dispatcher is more interesting. You pass all the Stores to it, and it composes them into a single Store function that it uses for computation. The dispatcher is also a pure function, and it is passed as configuration to `createRedux`, the only stateful thing in Redux. By default, the default dispatcher is used, so if you call `createRedux(stores)`, it is created implicitly. + +To sum it up: there is a Redux instance at the root of your app. It binds everything together. It accepts a dispatcher (which itself accepts Stores), it holds the state, and it knows how to turn actions into state updates. Everything else (components, for example) subscribes to the Redux instance. If something wants to dispatch an action, they need to do it on the Redux instance. `Connector` is a handy shortcut for subscribing to a slice of the Redux instance's state and injecting `dispatch` into your components, but you don't have to use it. + +There is no other “interaction” in Redux. diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000000..62de027c34 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,250 @@ +# Getting Started + +Don't be fooled by all the fancy talk about reducers, middleware, higher-order stores — Redux is incredibly simple. If you've ever built a Flux application, you will feel right at home. (If you're new to Flux, it's easy, too!) + +In this guide, we'll walk through the process of creating a simple Todo app. + +## Actions + +First, let's define some actions. + +**Actions** are payloads of information that send data from your application to your store. They are the *only* source of information for a store. You send them to the store using `store.dispatch()`. + +Here's an example action which represents adding a new todo item: + +```js +{ + type: ADD_TODO, + payload: { + text: 'Build my first Redux app' + } +} +``` + +Actions are plain JavaScript objects. By convention, actions should have a `type` field that indicates the type of action being performed. Types should typically be defined as constants and imported from another module. + +```js +import { ADD_TODO, REMOVE_TODO } from '../actionTypes'; +``` + +Other than `type`, the structure of an action object is really up to you. If you're interested, check out [Flux Standard Action](https://github.com/acdlite/flux-standard-action) for recommendations on how actions should be constructed. + +We need one more action type, for removing todos: + +```js +{ + type: REMOVE_TODO, + payload: 123 +} +``` + +`payload` in this case indicates the id of the todo we want to remove. + +## Action creators + +**Action creators** exactly that — functions that create actions. It's easy to conflate the terms "action" and "action creator," so do your best to use the proper term. + +In *other* Flux implementations, action creators often trigger a dispatch when invoked, like so: + +```js +function addTodoWithDispatch(text) { + const action = { + type: ADD_TODO, + payload: { + text + } + }; + dispatch(action); +} +``` + +By contrast, in Redux action creators are **pure functions** with zero side-effects. They simply return an action: + +```js +function addTodo(text) { + return { + type: ADD_TODO, + payload: { + text + } + }; +} +``` + +This makes them more portable and easier to test. To actually initiate a dispatch, pass the result to the `dispatch()` function: + +```js +dispatch(addTodo(text)); +dispatch(removeTodo(id)); +``` + +Or create **bound action creator** that automatically dispatches: + +```js +const boundAddTodo = text => dispatch(addTodo(text)); +const boundRemoveTodo = id => dispatch(addTodo(id)); +``` + +This is an example of a **higher-order function**. You'll notice this pattern a lot when working with Redux. Don't be scared, though — a higher-order function is just a function that returns another function. As you can see, ES6 arrow functions make working with higher-order functions a breeze. + +The `dispatch()` function can be accessed directly from the store as `store.dispatch()`, but more likely you'll access it using a helper like react-redux's `connect()`. + +## Reducers + +Now let's set up our store to respond to the action we defined above. + +Unlike other Flux implementations, Redux only has a single store, rather than a different store for each domain of data in your application. Instead of creating multiple stores that manually manage their own internal state, we create **reducers** that specify how to calculate state in response to new actions. + +A reducer looks like this: + +```js +(previousState, action) => newState +``` + +It's the type of function you would pass to `Array.prototype.reduce(reducer, ?initialValue)`. + +This may seem radical, but it turns out that this function signature is sufficient to express the entirety of the store model from traditional Flux — in a purely functional way. **This is the essence of Redux**. It's what enables all the cool features like hot reloading, time travel, and universal rendering. Aside from all that, though, it's simply a better model for expressing state changes. + +[**See Dan's talk at React Europe for more on this topic**](https://www.youtube.com/watch?v=xsSnOQynTHs). + +Let's make a reducer for our Todo app: + +```js +const initialState = { todos: [], idCounter: 0 }; + +function todoReducer(state = initialState, action) { + switch (action.type) { + case ADD_TODO: + return { + ...state, + todos: [ + ...state.todos, + { text: action.payload, id: state.idCounter + 1 } + ], + idCounter: state.idCounter + 1 + }; + case REMOVE_TODO: + return { + ...state, + todos: state.todos.filter(todo => todo.id !== action.payload) + }; + default: + return state; + } +} +``` + +Whoa, what's going on here? A few things to note: + +- `state` is the previous state of the store. Redux will dispatch a dummy action immediately upon creating your store, at which point `state` is undefined. From that point on, `state` is the previous value returned by the reducer. +- We're using a default parameter to specify the initial state of the store. +- We're using a switch statement to check the action type. +- **We're not mutating the previous state** — we're returning a **new** state object based on the **previous** state object. + +That last point is especially important: never mutate the previous state object. Always return a new state. Remember, reducers are pure functions, and should not perform mutations or side-effects. Here we're using the ES7 spread operator to shallow copy old state values to a new object. You can use a library like Immutable.js for a nicer API and better performance, since it uses [persistent data structures](http://en.wikipedia.org/wiki/Persistent_data_structure). Here's how that same store would look using immutable values: + +```js +const initialState = Immutable.Map({ todos: [], idCounter: 0 }); + +function todoReducer(state = initialState, action) { + switch (action.type) { + case ADD_TODO: + return state + .update('todos', + todos => todos.push(Immutable.Map({ + text: action.payload, + id: state.get('idCounter') + }) + )) + .set('idCounter', state.get('idCounter') + 1); + case REMOVE_TODO: + return state + .update('todos', + todos => todos.filter(todo => todo.id !== action.payload ) + ); + default: + return state; + } +} +``` + +If you're thinking "yuck, switch statements," remember that reducers are just functions — you can abstract away these details using helpers. Check out [redux-actions](https://github.com/acdlite/redux-actions) for an example of how to use higher-order functions to create reducers. + +## Creating a store + +Now we have some action creators and a reducer to handle them. The next step is to create our store. + +To create a store, pass a reducer to `createStore()`: + +```js +import { createStore } from 'redux'; +import todoReducer from '../reducers/todos'; +const store = createStore(todoReducer); +``` + +Usually you'll have multiple reducers for different domains of data in your app. You can use the `combineReducers()` helper to combine multiple reducers into one: + +```js +import { createStore, combineReducers } from 'redux'; +import * as reducers from '../reducers'; +const reducer = combineReducers(reducers); +const store = createStore(reducer); +``` + +For example, if the object passed to `combineReducers()` looks like this: + +```js +const reducers = { + todos: todoReducer, + counter: counterReducer +}; +``` + +It will create a reducer which produces a state object like this: + +```js +const state = { + todos: todoState, + counter: counterState +}; +``` + +## Middleware + +Middleware is an optional feature of Redux that enables you to customize how dispatches are handled. Think of middleware as a certain type of plugin or extension for Redux. + +A common use for middleware is to enable asynchronous dispatches. For example, a promise middleware adds support for dispatching promises: + +```js +dispatch(Promise.resolve({ type: ADD_TODO, payload: { text: 'Use middleware!' } })); +``` +A promise middleware would detect that a promise was dispatched, intercept it, and instead dispatch with the resolved value at a future point in time. + +Middleware is very simple to create using function composition. We won't focus on how middleware works in this document but here's how you enable it when creating your store: + +```js +import { createStore, applyMiddleware } from 'redux'; +// where promise, thunk, and observable are examples of middleware +const store = applyMiddleware(promise, thunk, observable)(createStore)(reducer); +``` + +Yes, you read that correctly. [Read more about how middleware works here.](middleware.md) + +## Connecting to your views + +You'll rarely interact with the store object directly. Most often, you'll use some sort of binding to your preferred view library. + +Flux is most popular within the React community, but Redux works with any kind of view layer. The React bindings for Redux are available as react-redux — see that project for details on how to integrate with React. + +However, if you do find yourself needing to access the store directly, the API for doing so is very simple: + +- `store.dispatch()` dispatches an action. +- `store.getState()` gets the current state. +- `store.subscribe()` registers a listener which is called after every dispatch, and returns a function which you call to unsubscribe. + + +## Go make something great + +That's it! As you can see, despite the powerful features that Redux enables, the core of Redux is really quite simple. + +Please let us know if you have suggestions for how this guide could be improved. diff --git a/docs/higher-order-stores.md b/docs/higher-order-stores.md new file mode 100644 index 0000000000..cbd76a8c84 --- /dev/null +++ b/docs/higher-order-stores.md @@ -0,0 +1,65 @@ +Higher-order stores +=================== + +A higher-order store is a function that turns a store creating function into a new store creating function: + +``` +createStore => createStore' +``` + +Look familiar? It's like the signature for [middleware](middleware.md), only we're wrapping `createStore()` instead of `dispatch()`. Like middleware, the key feature of higher-order stores is that they are composable. + +Higher-order stores are much the same as higher-order components in React. They can be as simple as `applyMiddleware()`, or as powerful as the [Redux Devtools](https://github.com/gaearon/redux-devtools). There's no limit to the kinds of extensions you can create. + +## How it works + +Let's look at an example. As alluded to above, `applyMiddleware()` is an example of a higher-order store. You use it by wrapping the base `createStore()` function provided by Redux: + +```js +const newCreateStore = applyMiddleware(m1, m2, m3)(createStore); +``` + +Internally, `applyMiddleware()` works by proxying the `dispatch()` method returned by `createStore()`: + +```js +// Implementation has been simplified for the purpose of illustration +export default function applyMiddleware(...middlewares) { + // ...combine middlewares... + + return next => (reducer, initialState) => { + const store = next(reducer, initialState); + return { + ...store, + dispatch: middleware(store.dispatch) + }; + }; +} +``` + +`next` is the next store creating function in the chain — either the return value of another higher-order store, or `createStore()` itself. + +This design allows for multiple higher-order stores to be used together using function composition. + +```js +const newCreateStore = compose( + applyMiddleware(m1, m2, m3), + devTools, + createStore +); +``` + +Now just pass your reducer (and an initial state, if desired) to your new store creating function: + +```js +const store = newCreateStore(reducer, intialState); + + +``` + +## Creating higher-order stores + +The signature of a higher-order store looks like this: + +## Middleware versus higher-order stores + +Middleware and higher-order stores are conceptually similar. Both wrap around the store interface to modify its behavior in a composable way. The difference is that middleware is exclusively concerned with modifying the behavior of `dispatch()`, whereas higher-order stores can modify any part of the store interface. diff --git a/docs/middleware.md b/docs/middleware.md index 7de0ee8bce..5db9d4d0b9 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -1,4 +1,56 @@ -# Middleware +Middleware +========== + +A middleware in Redux is a function that turns a dispatching function into a new dispatching function: + +``` +dispatch => dispatch' +``` + +The key feature of middleware is that it is composable. Multiple middleware can be combined together, where each middleware requires no knowledge of the what comes before or after it in the chain. + +Usage +===== + +To enable middleware in your Redux app, use `applyMiddleware()`. + +### `applyMiddleware(...middlewares)` + +This function returns a [higher-order store](higher-order-stores.md). You don't need to worry about that if you're not interested — here's how you use it: + +```js +const store = applyMiddleware(thunk, promise, observable)(createStore)(reducer); +``` + +Yes, you read that correctly. If this looks strange to you, it may help to break the process down into multiple steps: + +```js +const newCreateStore = applyMiddleware(thunk, promise, observable)(createStore); +const store = newCreateStore(reducer); +``` + +If you + +How it works +============ + +```js +const newDispatch = thunk(promise(observable(dispatch))); +// Or +const newDispatch = compose(thunk, promise, observable, dispatch); +``` + +`compose` performs function composition. It is the same as `compose()` in underscore or lodash. + +You can also use `composeMiddleware()`, which is similar to `compose()` except instead of creating a dispatching function, it creates a middleware function: + +```js +const middleware = composeMiddleware(thunk, promise, observable); +const newDispatch = compose(middleware, dispatch); +// Or simply +const newDispatch = compose(dispatch); +``` + A middleware is a function that wraps the `dispatch()` method, or another middleware. For example: diff --git a/docs/react.md b/docs/react.md new file mode 100644 index 0000000000..6d7f91effc --- /dev/null +++ b/docs/react.md @@ -0,0 +1,101 @@ +React bindings reside in the [react-redux](https://github.com/gaearon/react-redux) repository. + +### Components + +#### Dumb Components + +```js +// 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 ( +

+ Clicked: {counter} times + {' '} + + {' '} + +

+ ); + } +} +``` + +#### Smart Components + +```js +// The smart component may observe stores using ``, +// and bind actions to the dispatcher with `bindActionCreators`. + +import React from 'react'; +import { bindActionCreators } from 'redux'; +import { Connector } from 'react-redux'; +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 ( + + {({ counter, dispatch }) => + /* Yes this is child as a function. */ + + } + + ); + } +} +``` + +#### Decorators + +The `@connect` decorator lets you create smart components less verbosely: + +```js +import React from 'react'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +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 ( + + ); + } +} +``` + +### React Native + +To use Redux with React Native, just replace imports from `react-redux` with `react-redux/native`: + +```js +import { bindActionCreators } from 'redux'; +import { Provider, Connector } from 'react-redux/native'; +``` diff --git a/docs/store.md b/docs/store.md new file mode 100644 index 0000000000..9e1fec5a1a --- /dev/null +++ b/docs/store.md @@ -0,0 +1,157 @@ +### The Redux Store + +The store has the following responsibilities: + +* Holds application state +* Allows access to state via `getState` +* Allows state to be updated via `dispatch` +* Registers listeners via `subscribe` + +####Initialization + +To intialize a store you simply call `createStore` with a reducer: + +```js +import { createStore } from 'redux'; +import counterReducer from './reducers/counter'; + +const store = createStore(counterReducer); +``` + +`createStore` intializes the store with a single reducer, but in the following example we would like to use functionality from both the `counter` and `todos` reducers. To do this we need to somehow combine `counter` and `todos` into a single reducer. Here is one approach: + +```js +import { createStore } from 'redux'; +import counterReducer from './reducers/counter'; +import todosReducer from './reducers/todos'; + +// set up the initial combined state +const initialState = { + counterState: undefined, + todoState: undefined +}; + +function masterReducer(state = initialState, action) { + // call each reducer separately + const counterState = counterReducer(state.counterState, action); + const todoState = todosReducer(state.todoState, action); + + // combine updated state created by each reducer into the new combined state + return { counterState, todoState }; +} + +const store = createStore(masterReducer); +``` + +Combining reducers is very common so there is a helper function named `combineReducers` to assist. `combineReducers` takes an object of reducers and combines them into a single reducer. Here is the previous example using `combineReducers`: + +```js +import { createStore, combineReducers } from 'redux'; +import counterReducer from './reducers/counter'; +import todosReducer from './reducers/todos'; + +const reducers = { + counter: counterReducer, + todos: todosReducer +} + +const masterReducer = combineReducers(reducers); +const store = createStore(masterReducer); +``` + +Note that the key of each reducer in the reducer object passed to `combineReducers` becomes a top-level key on the state object returned by the combined reducer. In the previous example, the state object returned by `masterReducer` looks like this: + +```js +const state = { + counter: counterState, + todos: todosState +}; +``` + +A recommended pattern is to use `import *` to import an object of reducers from a definition file: + +```js +// reducers/index.js +export { default as counter } from './counter' +export { default as todos } from './todos' +``` + +```js +import { createStore, combineReducers } from 'redux'; +import * as reducers from './reducers/index'; + +const reducer = combineReducers(reducers); +const store = createStore(reducer); +``` + +You may optionally specify the initial state as the second argument to `createStore`. This is useful for hydrating the state of the client to match the state of a Redux application running on the server. + +```js +// server +const store = createStore(reducer); +store.dispatch(MyActionCreators.doSomething()); // fire action creators to fill the state +const state = store.getState(); // somehow pass this state to the client + +// client +const initialState = window.STATE_FROM_SERVER; +const store = createStore(reducer, initialState); +``` + +####Usage + +Store state is accessed using the `getState` method. + +```js +store.getState(); +// { +// counter: 0, +// todos: [{ +// text: 'Use Redux', +// marked: false, +// id: 0 +// }]; +// } +``` + +Store state is updated by calling the `dispatch` method with an action understood by the reducer. + +```js +store.dispatch({ + type: INCREMENT_COUNTER +}); +store.dispatch({ + type: MARK_TODO, + id: 0 +}); +``` +```js +store.getState(); +// { +// counter: 1, +// todos: [{ +// text: 'Use Redux', +// marked: true, +// id: 0 +// }]; +// } +``` + +A listener can be registered with the store by passing a callback to the `subscribe` method. The `subscribe` method returns a function that can later be called to unsubscribe the listener. + +```js +let unsubscribe = store.subscribe(() => console.log('state change!')); +``` + +####Advanced Intitialization + +[Middleware](middleware.md) can be set up using `applyMiddleware`. + +```js +import { createStore, applyMiddleware, combineReducers } from 'redux' +import { logger, promise } from './middleware' +import * as reducers from './reducers/index'; + +const reducer = combineReducers(reducers); +const createStoreWithMiddleware = applyMiddleware(logger, promise)(createStore); +const store = createStoreWithMiddleware(reducer); +``` diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 0000000000..464090415c --- /dev/null +++ b/docs/testing.md @@ -0,0 +1 @@ +# TODO diff --git a/docs/types-and-terminology.md b/docs/types-and-terminology.md new file mode 100644 index 0000000000..977f5e6228 --- /dev/null +++ b/docs/types-and-terminology.md @@ -0,0 +1,107 @@ +Types and terminology +===================== + +This is a glossary of the core terms in Redux, along with their type signatures. Types are documented using [Flow notation](http://flowtype.org/docs/quick-reference.html#_). + +### State + +```js +type State = any +``` + +**State** is a broad term, but in the Redux API it usually refers to the single state value that is managed by the store and returned by `getState()`. It is composed of nested properties and represents the entire state of the Redux store. By convention, the top-level state is an an object or some other key-value collection like a Map, but technically it can be any type. + +### Action + +```js +type Action = Object +``` + +An **action** is a payload of information that is used to accumulate state. The application uses actions to send data to the store. By convention, they contain a `type` property which indicates the nature of the action being dispatched. + +See also **intermediate actions** below. + +### Dispatching function + +```js +type Dispatch = (a: Action | IntermediateAction) => any +``` + +A **dispatching function** (or simply **dispatch function**) is a function that accepts an action or an intermediate action; it then may or may not dispatch one or more actions to the store. + +We must distinguish between dispatching functions in general and the base dispatch function provided by the Store interface. The base dispatch function *always* sends an action to the store's reducer, along with the previous state returned by the store, to calculate a new state. However, the application will rarely call the base dispatch function directly — usually, the base dispatch function is wrapped by middleware. See below for more information. + +### Reducer + +```js +type Reducer = (state: S, action: A) => S +``` + +A **reducer** or **reducing function** is a function that accepts an accumulation and a value and returns a new accumulation. They are used to reduce a collection of values down to a single value. Reducers are not unique to Redux — they are a fundamental concept in functional programming. Even most non-functional languages, like JavaScript, have a built-in API for reducing. (In JavaScript, it's `Array.prototype.reduce()`.) + +In Redux, the accumulated value is the state object, and the values being accumulated are actions. Reducers calculate a new state given the previous state and an action. They must be *pure functions* — functions that return the exact same output for given inputs. They should also be free of side-effects. This is what enables exciting features like hot reloading and time travel. + +Reducers are the most important concept in Redux. + +### Action creator + +```js +type ActionCreator = (...args: any) => Action | IntermediateAction +``` + +An action creator is, quite simply, a function that creates an action. Do not confuse the two terms — again, an action is a payload of information, and an action creator is a factory that creates them. + +### Intermediate action + +```js +type IntermediateAction = any +``` + +An **intermediate action** is a value that is sent to a dispatching function, but is not yet ready for consumption by the reducer; it will be transformed by middleware before being sent to the base `dispatch()` function. Intermediate actions are often asynchronous primitives, like a promise or a thunk, which are not dispatched themselves, but trigger dispatches once an operation has completed. + +### Middleware + +```js +type Middleware = (methods: { dispatch: Dispatch, getState: () => State }) => (next: Dispatch) => Dispatch; +``` + +A middleware is a higher-order function that composes a dispatch function to return a new dispatch function. + +- The outermost function receives an object of methods which are a subset of the Store interface: `dispatch()` and `getState()`. This gives the inner function access to these methods. +- That returns another function, which receives a dispatch function. This dispatch function is not necessarily the same as the base dispatch function passed to the outermost function — it is the next dispatch function in the middleware chain. +- The innermost function is a dispatch function. It receives an action, and can either call the next dispatch function in the chain, or call the base dispatch function to restart the chain. It can call either function asynchronously and multiple times, or it can call nothing at all. A no-op middleware should synchronously call `next(action)`. + +Middleware is composable using function composition. + +### Store + +```js +type Store = { dispatch: Dispatch, getState: () => State, subscribe: () => Function, getReducer: () => Reducer, replaceReducer: (reducer: Reducer) => void } +``` + +A store is an object of bound methods to an underlying class instance. + +- `dispatch()` is the base dispatch function described above. +- `getState()` returns the current state of the store. +- `subscribe()` registers a function to be called on state changes. It returns an unsubscribe function. +- `getReducer()` and `replaceReducer()` are used to implement hot reloading, and should not be used directly. + +### Store-creating function + +```js +type CreateStore = (reducer: Function, initialState: any) => Store +``` + +A store-creating function is a function that creates a Redux store. Like with dispatching function, we must distinguish the base store-creating function, `createStore()`, from store-creating functions that are returned from higher-order stores. + +### Higher-order store + +```js +type HigherOrderStore = (next: CreateStore) => CreateStore +``` + +A higher-order store is a higher-order function that composes a store-creating function to return a new store-creating function. This is similar to middleware in that it allows you to alter the store interface in a composable way. + +Higher-order stores are much the same concept as higher-order components in React. + +Because a store is not an instance, but rather an plain-object collection of bound methods, copies can be easily created and modified without mutating the original store. diff --git a/docs/universal-rendering.md b/docs/universal-rendering.md new file mode 100644 index 0000000000..5099933e35 --- /dev/null +++ b/docs/universal-rendering.md @@ -0,0 +1,16 @@ +### 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: + +```js +// 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); +``` diff --git a/src/middleware/thunk.js b/src/middleware/thunk.js index a80a78159c..4500e5aeb0 100644 --- a/src/middleware/thunk.js +++ b/src/middleware/thunk.js @@ -1,3 +1,12 @@ +/** + * Given an action that returns a function, delegates its call + * to the function itself by passing the control of the dispatching, + * otherwise simply dispatch the action. + * In other words, it allows to have async actions. + * + * @param {Function} getState - allow to access the current state + * @return {Function} a new middleware + */ export default function thunkMiddleware(getState) { return (next) => { const recurse = (action) => diff --git a/src/utils/bindActionCreators.js b/src/utils/bindActionCreators.js index f9a81a5a80..e4a757b980 100644 --- a/src/utils/bindActionCreators.js +++ b/src/utils/bindActionCreators.js @@ -1,5 +1,13 @@ import mapValues from '../utils/mapValues'; +/** + * Given a list action creators, wrap them to the `dispatch` function + * in order to be automatically dispatched when invoked. + * + * @param {Object} actionCreators - an object with the action functions + * @param {Function} dispatch + * @return {Object} the given object with wrapped actions + */ export default function bindActionCreators(actionCreators, dispatch) { return mapValues(actionCreators, actionCreator => (...args) => dispatch(actionCreator(...args)) diff --git a/src/utils/composeMiddleware.js b/src/utils/composeMiddleware.js index 596403919d..4f9f47f3e8 100644 --- a/src/utils/composeMiddleware.js +++ b/src/utils/composeMiddleware.js @@ -1,3 +1,9 @@ +/** + * Given a list of middlewares, compose them from left to right. + * + * @param {Array} middlewares - a list of middleware functions + * @return {Function} the combined middleware function + */ export default function composeMiddleware(...middlewares) { return middlewares.reduceRight((composed, m) => m(composed)); } diff --git a/src/utils/composeStores.js b/src/utils/composeStores.js index d8c4420546..ebed3f1baf 100644 --- a/src/utils/composeStores.js +++ b/src/utils/composeStores.js @@ -1,6 +1,16 @@ import mapValues from '../utils/mapValues'; import pick from '../utils/pick'; +/** + * Given a list of stores, maps the state keys to reducer functions. + * The composed store, when invoked, will internally map all the + * results of the computed stores (being effectively the state). + * When the store function is invoked again, it will pass the + * existing state value as initial state. + * + * @param {Object} stores - an object with the store reducer functions + * @return {Function} the composed store function + */ export default function composeStores(stores) { const finalStores = pick(stores, (val) => typeof val === 'function'); return function Composition(atom = {}, action) { diff --git a/src/utils/createReduxShape.js b/src/utils/createReduxShape.js index e3795bedd2..d79e1b7f98 100644 --- a/src/utils/createReduxShape.js +++ b/src/utils/createReduxShape.js @@ -1,3 +1,9 @@ +/** + * Define the shape of the `redux` prop, used for props validation. + * + * @param {Object} PropTypes + * @return {Object} + */ export default function createReduxShape(PropTypes) { return PropTypes.shape({ subscribe: PropTypes.func.isRequired, diff --git a/src/utils/getDisplayName.js b/src/utils/getDisplayName.js index 512702c87a..54f3263588 100644 --- a/src/utils/getDisplayName.js +++ b/src/utils/getDisplayName.js @@ -1,3 +1,9 @@ +/** + * Given a React component, return its name to be displayed. + * + * @param {React} Component + * @return {String} the name of the component + */ export default function getDisplayName(Component) { return Component.displayName || Component.name || 'Component'; } diff --git a/src/utils/identity.js b/src/utils/identity.js index 8c690a8859..f342078921 100644 --- a/src/utils/identity.js +++ b/src/utils/identity.js @@ -1,3 +1,9 @@ +/** + * Returns the first argument provided to it. + * + * @param {*} value + * @return {*} + */ export default function identity(value) { return value; } diff --git a/src/utils/isPlainObject.js b/src/utils/isPlainObject.js index a5845486cf..1a312c5d3f 100644 --- a/src/utils/isPlainObject.js +++ b/src/utils/isPlainObject.js @@ -1,3 +1,11 @@ +/** + * Given an object, checks that it's a plain object. That is, an object + * created by the `Object` constructor or one with a `Prototype` of `null`. + * + * @param {*} obj - the value to check + * @return {Boolean} `true` if it's a plain object, else `false` + */ export default function isPlainObject(obj) { - return obj ? typeof obj === 'object' && Object.getPrototypeOf(obj) === Object.prototype : false; + return obj ? typeof obj === 'object' + && Object.getPrototypeOf(obj) === Object.prototype : false; } diff --git a/src/utils/mapValues.js b/src/utils/mapValues.js index 29d203cf61..6b2d93e75d 100644 --- a/src/utils/mapValues.js +++ b/src/utils/mapValues.js @@ -1,3 +1,12 @@ +/** + * Given an object, returns a new object with the same keys + * and values generated by running each own enumerable property + * of the object through an `iteratee` function. + * + * @param {Object} obj + * @param {Function} fn - invoked to map the new value + * @return {Object} + */ export default function mapValues(obj, fn) { return Object.keys(obj).reduce((result, key) => { result[key] = fn(obj[key], key); diff --git a/src/utils/pick.js b/src/utils/pick.js index 2c9719c1c0..dfa364fcc4 100644 --- a/src/utils/pick.js +++ b/src/utils/pick.js @@ -1,3 +1,12 @@ +/** + * Given an object, returns a new object with the same keys + * and values filtered by the result of the `iteratee` function. + * + * @param {Object} obj + * @param {Function} fn - invoked to determine whether to discard + * the property or not + * @return {Object} + */ export default function pick(obj, fn) { return Object.keys(obj).reduce((result, key) => { if (fn(obj[key])) { diff --git a/src/utils/shallowEqual.js b/src/utils/shallowEqual.js index f82be71949..2ac60c423d 100644 --- a/src/utils/shallowEqual.js +++ b/src/utils/shallowEqual.js @@ -1,3 +1,13 @@ +/** + * Given two objects, performs equality by iterating through keys + * on an object and returning `false` when any key has values which + * are not strictly equal between `objA` and `objB`. + * Returns `true` when the values of all keys are strictly equal. + * + * @param {Object} objA + * @param {Object} objB + * @return {Boolean} + */ export default function shallowEqual(objA, objB) { if (objA === objB) { return true; diff --git a/src/utils/shallowEqualScalar.js b/src/utils/shallowEqualScalar.js index 2adb8ea85b..62535b52cb 100644 --- a/src/utils/shallowEqualScalar.js +++ b/src/utils/shallowEqualScalar.js @@ -1,3 +1,16 @@ +/** + * Given two objects, performs equality by iterating through keys + * on an object and returning `false` when any key has values which + * are not strictly equal between `objA` and `objB`. + * Returns `true` when the values of all keys are strictly equal. + * + * NOTE: if value is an `Object`, returns `false`. This allows the check + * to be more performant. + * + * @param {Object} objA + * @param {Object} objB + * @return {Boolean} + */ export default function shallowEqualScalar(objA, objB) { if (objA === objB) { return true;