diff --git a/docs/using-react-redux/connect-dispatching-actions-with-mapDispatchToProps.md b/docs/using-react-redux/connect-dispatching-actions-with-mapDispatchToProps.md new file mode 100644 index 000000000..b8053a2b7 --- /dev/null +++ b/docs/using-react-redux/connect-dispatching-actions-with-mapDispatchToProps.md @@ -0,0 +1,406 @@ +--- +id: connect-dispatching-actions-with-mapDispatchToProps +title: Connect: Dispatching Actions with mapDispatchToProps +hide_title: true +sidebar_label: Connect: Dispatching Actions with mapDispatchToProps +--- + +# Connect: Dispatching Actions with `mapDispatchToProps` + +As the second argument passed in to `connect`, `mapDispatchToProps` is used for dispatching actions to the store. + +`dispatch` is a function of the Redux store. You call `store.dispatch` to dispatch an action. +This is the only way to trigger a state change. + +With React-Redux, your components never access the store directly - `connect` does it for you. +React-Redux gives you two ways to let components dispatch actions: + +- By default, a connected component receives `props.dispatch` and can dispatch actions itself. +- `connect` can accept an argument called `mapDispatchToProps`, which lets you create functions that dispatch when called, and pass those functions as props to your component. + +The `mapDispatchToProps` functions are normally referred to as `mapDispatch` for short, but the actual variable name used can be whatever you want. + +## Approaches for Dispatching + +### Default: `dispatch` as a Prop + +If you don't specify the second argument to `connect()`, your component will receive `dispatch` by default. For example: + +```js +connect()(MyComponent); +// which is equivalent with +connect( + null, + null +)(MyComponent); + +// or +connect(mapStateToProps /** no second argument */)(MyComponent); +``` + +Once you have connected your component in this way, your component receives `props.dispatch`. You may use it to dispatch actions to the store. + +```js +function Counter({ count, dispatch }) { + return ( +
+ + {count} + + +
+ ); +} +``` + +### Providing A `mapDispatchToProps` Parameter + +Providing a `mapDispatchToProps` allows you to specify which actions your component might need to dispatch. It lets you provide action dispatching functions as props. Therefore, instead of calling `props.dispatch(() => increment())`, you may call `props.increment()` directly. There are a few reasons why you might want to do that. + +#### More Declarative + +First, encapsulating the dispatch logic into function makes the implementation more declarative. +Dispatching an action and letting the Redux store handle the data flow is _how to_ implement the behavior, rather than _what_ it does. + +A good example would be dispatching an action when a button is clicked. Connecting the button directly probably doesn't make sense conceptually, and neither does having the button reference `dispatch`. + +```js +// button needs to be aware of "dispatch" + + {count} + + + + ); +} +``` + +(Full code of the Counter example is [in this CodeSandbox](https://codesandbox.io/s/yv6kqo1yw9)) + +### Defining the `mapDispatchToProps` Function with `bindActionCreators` + +Wrapping these functions by hand is tedious, so Redux provides a function to simplify that. + +> `bindActionCreators` turns an object whose values are [action creators](https://redux.js.org/glossary#action-creator), into an object with the same keys, but with every action creator wrapped into a [`dispatch`](https://redux.js.org/api/store#dispatch) call so they may be invoked directly. See [Redux Docs on `bindActionCreators`](http://redux.js.org/docs/api/bindActionCreators.html) + +`bindActionCreators` accepts two parameters: + +1. A **`function`** (an action creator) or an **`object`** (each field an action creator) +2. `dispatch` + +The wrapper functions generated by `bindActionCreators` will automatically forward all of their arguments, so you don't need to do that by hand. + +```js +import { bindActionCreators } from "redux"; + +const increment = () => ({ type: "INCREMENT" }); +const decrement = () => ({ type: "DECREMENT" }); +const reset = () => ({ type: "RESET" }); + +// binding an action creator +// returns (...args) => dispatch(increment(...args)) +const boundIncrement = bindActionCreators(increment, dispatch); + +// binding an object full of action creators +const boundActionCreators = bindActionCreators({ increment, decrement, reset }, dispatch); +// returns +// { +// increment: (...args) => dispatch(increment(...args)), +// decrement: (...args) => dispatch(decrement(...args)), +// reset: (...args) => dispatch(reset(...args)), +// } +``` + +To use `bindActionCreators` in our `mapDispatchToProps` function: + +```js +import { bindActionCreators } from "redux"; +// ... + +function mapDispatchToProps(dispatch) { + return bindActionCreators({ increment, decrement, reset }, dispatch); +} + +// component receives props.increment, props.decrement, props.reset +connect( + null, + mapDispatchToProps +)(Counter); +``` + +### Manually Injecting `dispatch` + +If the `mapDispatchToProps` argument is supplied, the component will no longer receive the default `dispatch`. You may bring it back by adding it manually to the return of your `mapDispatchToProps`, although most of the time you shouldn’t need to do this: + +```js +import { bindActionCreators } from "redux"; +// ... + +function mapDispatchToProps(dispatch) { + return { + dispatch, + ...bindActionCreators({ increment, decrement, reset }, dispatch) + }; +} +``` + +## Defining `mapDispatchToProps` As An Object + +You’ve seen that the setup for dispatching Redux actions in a React component follows a very similar process: define an action creator, wrap it in another function that looks like `(…args) => dispatch(actionCreator(…args))`, and pass that wrapper function as a prop to your component. + +Because this is so common, `connect` supports an “object shorthand” form for the `mapDispatchToProps` argument: if you pass an object full of action creators instead of a function, `connect` will automatically call `bindActionCreators` for you internally. + +**We recommend always using the “object shorthand” form of `mapDispatchToProps`, unless you have a specific reason to customize the dispatching behavior.** + +Note that: + +- Each field of the `mapDispatchToProps` object is assumed to be an action creator +- Your component will no longer receive `dispatch` as a prop + +```js +// React-Redux does this for you automatically: +dispatch => bindActionCreators(mapDispatchToProps, dispatch); +``` + +Therefore, our `mapDispatchToProps` can simply be: + +```js +const mapDispatchToProps = { + increment, + decrement, + reset +}; +``` + +Since the actual name of the variable is up to you, you might want to give it a name like `actionCreators`, or even define the object inline in the call to `connect`: + +```js +import {increment, decrement, reset} from "./counterActions"; + +const actionCreators = { + increment, + decrement, + reset +} + +export default connect(mapState, actionCreators)(Counter); + +// or +export default connect( + mapState, + { increment, decrement, reset } +)(Counter); +``` + +## Common Problems + +### Why is my component not receiving `dispatch`? + +Also known as + +```js +TypeError: this.props.dispatch is not a function +``` + +This is a common error that happens when you try to call `this.props.dispatch` , but `dispatch` is not injected to your component. + +`dispatch` is injected to your component _only_ when: + +**1. You do not provide `mapDispatchToProps`** + +The default `mapDispatchToProps` is simply `dispatch => ({ dispatch })`. If you do not provide `mapDispatchToProps`, `dispatch` will be provided as mentioned above. + +In another words, if you do: + +```js +// component receives `dispatch` +connect(mapStateToProps /** no second argument*/)(Component); +``` + +**2. Your customized `mapDispatchToProps` function return specifically contains `dispatch`** + +You may bring back `dispatch` by providing your customized `mapDispatchToProps` function: + +```js +const mapDispatchToProps = dispatch => { + return { + increment: () => dispatch(increment()), + decrement: () => dispatch(decrement()), + reset: () => dispatch(reset()), + dispatch + }; +}; +``` + +Or alternatively, with `bindActionCreators`: + +```js +import { bindActionCreators } from "redux"; + +function mapDispatchToProps(dispatch) { + return { + dispatch, + ...bindActionCreators({ increment, decrement, reset }, dispatch) + }; +} +``` + +See [this error in action in Redux’s GitHub issue #255](https://github.com/reduxjs/react-redux/issues/255). + +There are discussions regarding whether to provide `dispatch` to your components when you specify `mapDispatchToProps` ( [Dan Abramov’s response to #255](https://github.com/reduxjs/react-redux/issues/255#issuecomment-172089874) ). You may read them for further understanding of the current implementation intention. + +### Can I `mapDispatchToProps` without `mapStateToProps` in Redux? + +Yes. You can skip the first parameter by passing `undefined` or `null`. Your component will not subscribe to the store, and will still receive the dispatch props defined by `mapStateToProps`. + +```js +connect( + null, + mapDispatchToProps +)(MyComponent); +``` + +### Can I call `store.dispatch`? + +It's an anti-pattern to interact with the store directly in a React component, whether it's an explicit import of the store or accessing it via context (see the [Redux FAQ entry on store setup](https://redux.js.org/faq/storesetup#can-or-should-i-create-multiple-stores-can-i-import-my-store-directly-and-use-it-in-components-myself) for more details). Let React-Redux’s `connect` handle the access to the store, and use the `dispatch` it passes to the props to dispatch actions. + +## Links and References + +**Tutorials** + +- [You Might Not Need the `mapDispatchToProps` Function](https://daveceddia.com/redux-mapdispatchtoprops-object-form/) + +**Related Docs** + +- [Redux Doc on `bindActionCreators`](https://redux.js.org/api/bindactioncreators) + +**Q&A** + +- [How to get simple dispatch from `this.props` using connect with Redux?](https://stackoverflow.com/questions/34458261/how-to-get-simple-dispatch-from-this-props-using-connect-w-redux) +- [`this.props.dispatch` is `undefined` if using `mapDispatchToProps`](https://github.com/reduxjs/react-redux/issues/255) +- [Do not call `store.dispatch`, call `this.props.dispatch` injected by `connect` instead](https://github.com/reduxjs/redux/issues/916) +- [Can I `mapDispatchToProps` without `mapStateToProps` in Redux?](https://stackoverflow.com/questions/47657365/can-i-mapdispatchtoprops-without-mapstatetoprops-in-redux) +- [Redux Doc FAQ: React Redux](https://redux.js.org/faq/reactredux) diff --git a/docs/using-react-redux/connect-extracting-data-with-mapStateToProps.md b/docs/using-react-redux/connect-extracting-data-with-mapStateToProps.md index f1ca2557e..8dbf33f1f 100644 --- a/docs/using-react-redux/connect-extracting-data-with-mapStateToProps.md +++ b/docs/using-react-redux/connect-extracting-data-with-mapStateToProps.md @@ -28,6 +28,9 @@ This function should be passed as the first argument to `connect`, and will be c ### Arguments +1. **`state`** +2. **`ownProps` (optional)** + #### `state` The first argument to a `mapStateToProps` function is the entire Redux store state (the same value returned by a call to `store.getState()`). Because of this, the first argument is traditionally just called `state`. (While you can give the argument any name you want, calling it `store` would be incorrect - it's the "state value", not the "store instance".) @@ -131,13 +134,13 @@ To summarize the behavior of the component wrapped by `connect` with `mapStateTo | | `(state) => stateProps` | `(state, ownProps) => stateProps` | | ---------------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------- | -| `mapStateToProps` runs when: | store `state` changes | store `state` changes
or
any field of `ownProps` is different | +| `mapStateToProps` runs when: | store `state` changes | store `state` changes
or
any field of `ownProps` is different | | component re-renders when: | any field of `stateProps` is different | any field of `stateProps` is different
or
any field of `ownProps` is different | ### Only Return New Object References If Needed -React-Redux does shallow comparisons to see if the `mapState` results have changed. It’s easy to accidentally return new object or array references every time, which would cause your component to re-render even if the data is actually the same. +React-Redux does shallow comparisons to see if the `mapStateToProps` results have changed. It’s easy to accidentally return new object or array references every time, which would cause your component to re-render even if the data is actually the same. Many common operations result in new object or array references being created: @@ -147,7 +150,7 @@ Many common operations result in new object or array references being created: - Copying values with `Object.assign` - Copying values with the spread operator `{ ...oldState, ...newData }` -Put these operations in [memoized selector functions]() to ensure that they only run if the input values have changed. This will also ensure that if the input values _haven't_ changed, `mapState` will still return the same result values as before, and `connect` can skip re-rendering. +Put these operations in [memoized selector functions]() to ensure that they only run if the input values have changed. This will also ensure that if the input values _haven't_ changed, `mapStateToProps` will still return the same result values as before, and `connect` can skip re-rendering. diff --git a/website/sidebars.json b/website/sidebars.json index 665f0a2ab..17b28d34b 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -2,7 +2,8 @@ "docs": { "Introduction": ["introduction/quick-start", "introduction/basic-tutorial"], "Using React-Redux": [ - "using-react-redux/connect-extracting-data-with-mapStateToProps" + "using-react-redux/connect-extracting-data-with-mapStateToProps", + "using-react-redux/connect-dispatching-actions-with-mapDispatchToProps" ], "API Reference": ["api", "api/provider"], "Guides": ["troubleshooting"]