While working with React and Redux I encountered this interesting observation.
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {fetchBooks} from '../actions'
class Sample extends Component {
componentDidMount() {
/* Fetching from server */
}
render(){
return(
{this.props.books.map(book => <Book key={book.title} {...book} />)}
)
}
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({fetchBooks}, dispatch);
}
const mapStateToProps = ({books}) => {
return {books};
}
connect(mapStateToProps, mapDispatchToProps)(Sample)
When I implement componentDidMount() function with redux as
...
componentDidMount() {
this.props.fetchBooks();
}
...
the state is updated and the render function is invoked.
However, when I use axios with promise, it doesn't work:
import axios from 'axios';
...
componentDidMount() {
const self = this;
axios.get(__URL__)
.then((response) => {
self.setState({books: response.data});
});
}
...
Does this mean you cannot mixin the two ways to set state when you use redux?
Or why does one method work while the other doesn't work when redux is used?
Well, you miss mess with a component state and redux state. when you call dispatch function using redux:
componentDidMount() {
this.props.fetchBooks();
}
It dispatches the action function and redux state updated. The redux state catch from component props by passing the first argument at
connect(mapStateToProps, mapDispatchToProps)(Sample)
when you call setState after get axios response, it update your component state not redux state. so, in that case yon may find your data from this.state.books not this.props.books. If you need to dispatch action function when axios get response.
componentDidMount() {
const self = this;
axios.get(__URL__)
.then((response) => {
//self.setState({books: response.data});
self.props.yourDispatchFunctionWithPassingTheReponseData(response.data);
});
}
more information about dispatch, please check out the link
I followed this beginner tutorial on redux: text, vid.
Everything works with the exception of the increment button. When I click the increment button I get this error:
Uncaught TypeError: Cannot read property 'props' of undefined
See what happens: gif
Why am I getting that error when I did "mapStateToProps"?
index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import Test_left from './eo_app.jsx';
import { Provider } from 'react-redux';
import { createStore } from "redux";
const initialState = {
count: 21
};
const reducer = (state = initialState, action) => {
console.log('reducer', action);
switch (action.type) {
case 'INCREMENT':
return { count: 55 };
default:
return state;
}
};
const store = createStore(reducer);
const App = () => (
<Provider store={store}>
<Test_left />
</Provider>
);
ReactDOM.render(<App />, document.getElementById('target'));
//// store.dispatch({type: 'INCREMENT' }); this will work as expected
eo_app.jsx
import React from 'react';
import { connect } from 'react-redux';
class Test_left extends React.Component {
constructor(props) {
super(props);
}
increment () {
this.props.dispatch({ type: 'INCREMENT' }); // <== ERROR IS RAISED HERE
}
render() {
console.log('props:', this.props)
return (
<React.Fragment>
<h1> WE GOT RENDERED </h1>
<h1>{this.props.count}</h1>
<button onClick={this.increment}> Increment </button>
</React.Fragment>
)
};
}
const mapStateToProps = state => ({
count: state.count
})
export default connect(mapStateToProps)(Test_left);
EDIT:
There are actually two issues:
The first is that you need to bind your increment function to the class itself. This is a common "gotcha," you can read about it here - https://reactjsnews.com/es6-gotchas
The second is, with react-redux and the connect function, you need to map the redux dispatch function to a prop. This is done with a second function you pass in your connect call: mapDispatchToProps.
Yours might look like:
const mapDispatchToProps = dispatch => ({
handleClick: () => dispatch({ type: 'INCREMENT' })
});
With those two changes, your component would then look like
class Test_left extends React.Component {
constructor(props) {
super(props);
// Bind `increment` to this class's scope
this.increment = this.increment.bind(this);
}
increment () {
// Use newly created prop function to dispatch our action to redux
this.props.handleClick();
}
// ...
}
const mapStateToProps = state => ({
count: state.count
});
const mapDispatchToProps = dispatch => ({
handleClick: () => dispatch({ type: 'INCREMENT' })
});
export default connect(mapStateToProps, mapDispatchToProps)(Test_left);
When you do: this.props.dispatch, it will call dispatch mapped with the connect. But you don't have map for the dispatch:
export default connect(mapStateToProps)(Test_left);
So, replace preceding code with:
export default connect(mapStateToProps, mapDispatchToState)(Test_left);
Now, define the function for mapDispatchToState and call the dispatch there.
The key issue I forgot to mention is that make sure to bind this because this will be undefined when you call inside a method. Or, you may assign a public class method:
increment = () => { // now, this will be accessible
PS: See this post why you need to use map dispatch instead of directly dispatching it. It makes sense not to mix the connect maps with direct dispatch.
This my util module, and when I use redux action it does not work.
import {openloading} from '../actions/loading'
export default function (e) {
openloading(e.font);
}
But in my react component it does work
Actions themselves do nothing, which is ironic given the name. Actions merely describe what is going to happen and how state will change. Change actually occurs when these instructions are dispatched to the reducer. As Paul said, you need access to the dispatch method.
Typically, you're calling your util module functions from within your components. In that case, you might want to pass the dispatch method as a parameter.
import { openloading } from '../actions/openloading'
export const myFn = (e, dispatch) => {
dispatch(openloading(e.font))
}
Your component should get wired up to Redux like so:
import React from 'react'
import { connect } from 'react-redux'
import { myFn } from 'somewhere'
const myComponent = ({ dispatch }) => {
myFn(e, dispatch)
return (
<div>
{ ...whatever }
</div>
)
}
const mapStateToProps = (state) => {
return { ...stuff }
}
const mapDispatchToProps = (dispatch) => {
return {
dispatch: dispatch
}
}
export default connect(mapStateToProps, mapDispatchToProps)(myComponent)
Note that dispatch is getting passed into the component as a prop in mapDispatchToProps.
Going through the react-redux docs, I'm trying to understand why the
todo example uses connect and mapDispatchToProps vs why the reddit example uses a more traditional render method & passing the dispatch through a handler as props to the child component. Is there a reason for this? I can only guess that it's because the former example has a container component correspond to only one presentational component whereas the latter example's container component contains two presentational components so it would not make sense to use connect (nor is it possible) on two components.
todo example :
const getVisibleTodos = (todos, filter) => {
...
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
reddit example:
class App extends Component {
...
handleChange(nextReddit) {
this.props.dispatch(selectReddit(nextReddit))
}
...
render() {
...
return (
<div>
<Picker value={selectedReddit}
onChange={this.handleChange}
options={[ 'reactjs', 'frontend' ]} />
<p>
...
It's perfectly okay to pass dispatch to your component unless you don't want your component to misuse the dispatch function and dispatch actions that are not supposed to be dispatched from that component!
If you want to limit your component, you don't want to pass dispatch directly to the component. You'll want to pass specific action creators through mapDispatchToProps.
I think it boils down to coding standards, really. If you decide to be strict on your components and not allow them to directly dispatch any action, you can use mapDispatchToProps to pass only specific action creators.
Bonus: In the first example, you're passing (id) => dispatch(toggleTodo(id)) function to your component. Try using bindActionCreators from redux instead of manually creating that function! Good luck.
UPDATE
export const dataLoadRequest = () => {
return {
type: 'DATA_LOAD_REQUEST',
}
}
In your Component.js file, you need to import two things.
import { dataLoadRequest } from 'actions.js';
import { bindActionCreators } from 'redux';
class Component extends React.Component{
...
componentDidMount(){
this.props.actions.dataLoadRequest();
}
...
}
const mapStateToProps = (state) => ({
...
});
const mapDispatchToProps = (dispatch) => ({
actions: bindActionCreators(dataLoadRequest, dispatch)
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Component);
I am building an small application with redux, react-redux, & react. For some reason when using mapDispatchToProps function in tandem with connect (react-redux binding) I receive a TypeError indicating that dispatch is not a function when I try to execute the resulting prop. When I call dispatch as a prop however (see the setAddr function in the provided code) it works.
I'm curious as to why this is, in the example TODO app in the redux docs the mapDispatchToProps method is setup the same way. When I console.log(dispatch) inside the function it says dispatch is type object. I could continue to use dispatch this way but I would feel better knowing why this is happening before I continue any further with redux. I am using webpack with babel-loaders to compile.
My Code:
import React, { PropTypes, Component } from 'react';
import { connect } from 'react-redux';
import { setAddresses } from '../actions.js';
import GeoCode from './geoCode.js';
import FlatButton from 'material-ui/lib/flat-button';
const Start = React.createClass({
propTypes: {
onSubmit: PropTypes.func.isRequired
},
setAddr: function(){
this.props.dispatch(
setAddresses({
pickup: this.refs.pickup.state.address,
dropoff: this.refs.dropoff.state.address
})
)
},
render: function() {
return (
<div>
<div className="row">
<div className="col-xs-6">
<GeoCode ref='pickup' />
</div>
<div className="col-xs-6">
<GeoCode ref='dropoff' />
</div>
</div>
<div className="row">
<div className="col-xs-6">
<FlatButton
label='Does Not Work'
onClick={this.props.onSubmit({
pickup: this.refs.pickup.state.address,
dropoff: this.refs.dropoff.state.address
})}
/>
</div>
<div className="col-xs-6">
<FlatButton
label='Works'
onClick={this.setAddr}
/>
</div>
</div>
</div>
);
}
})
const mapDispatchToProps = (dispatch) => {
return {
onSubmit: (data) => {
dispatch(setAddresses(data))
}
}
}
const StartContainer = connect(mapDispatchToProps)(Start)
export default StartContainer
If you want to use mapDispatchToProps without a mapStateToProps just use null for the first argument.
export default connect(null, mapDispatchToProps)(Start)
You are just missing the first argument to connect, which is the mapStateToProps method. Excerpt from the Redux todo app:
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
Use
const StartContainer = connect(null, mapDispatchToProps)(Start)
instead of
const StartContainer = connect(mapDispatchToProps)(Start)
I solved it by interchanging the arguments, I was using
export default connect(mapDispatchToProps, mapStateToProps)(Checkbox)
which is wrong. The mapStateToProps has to be the first argument:
export default connect(mapStateToProps, mapDispatchToProps)(Checkbox)
It sounds obvious now, but might help someone.
I needed an example using React.Component so I am posting it:
import React from 'react';
import * as Redux from 'react-redux';
class NavigationHeader extends React.Component {
}
const mapStateToProps = function (store) {
console.log(`mapStateToProps ${store}`);
return {
navigation: store.navigation
};
};
export default Redux.connect(mapStateToProps)(NavigationHeader);
Issue
Here are a couple of things to notice in order to understand the connected component's behavior in your code:
The Arity of connect Matters: connect(mapStateToProps, mapDispatchToProps)
React-Redux calls connect with the first argument mapStateToProps, and second argument mapDispatchToProps.
Therefore, although you've passed in your mapDispatchToProps, React-Redux in fact treats that as mapState because it is the first argument. You still get the injected onSubmit function in your component because the return of mapState is merged into your component's props. But that is not how mapDispatch is supposed to be injected.
You may use mapDispatch without defining mapState. Pass in null in place of mapState and your component will not subject to store changes.
Connected Component Receives dispatch by Default, When No mapDispatch Is Provided
Also, your component receives dispatch because it received null for its second position for mapDispatch. If you properly pass in mapDispatch, your component will not receive dispatch.
Common Practice
The above answers why the component behaved that way. Although, it is common practice that you simply pass in your action creator using mapStateToProps's object shorthand. And call that within your component's onSubmit That is:
import { setAddresses } from '../actions.js'
const Start = (props) => {
// ... omitted
return <div>
{/** omitted */}
<FlatButton
label='Does Not Work'
onClick={this.props.setAddresses({
pickup: this.refs.pickup.state.address,
dropoff: this.refs.dropoff.state.address
})}
/>
</div>
};
const mapStateToProps = { setAddresses };
export default connect(null, mapDispatchToProps)(Start)
A pitfall some might step into that is covered by this question but isn't addressed in the answers as it is slightly different in the code structure but returns the exact same error.
This error occurs when using bindActionCreators and not passing the dispatch function
Error Code
import someComponent from './someComponent'
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'
import { someAction } from '../../../actions/someAction'
const mapStatToProps = (state) => {
const { someState } = state.someState
return {
someState
}
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
someAction
});
};
export default connect(mapStatToProps, mapDispatchToProps)(someComponent)
Fixed Code
import someComponent from './someComponent'
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'
import { someAction } from '../../../actions/someAction'
const mapStatToProps = (state) => {
const { someState } = state.someState
return {
someState
}
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
someAction
}, dispatch);
};
export default connect(mapStatToProps, mapDispatchToProps)(someComponent)
The function dispatch was missing in the Error code
React-redux 'connect' function accepts two arguments first is mapStateToProps and second is mapDispatchToProps check below ex.
export default connect(mapStateToProps, mapDispatchToProps)(Index);
`
If we don't want retrieve state from redux then we set null instead of mapStateToProps.
export default connect(null, mapDispatchToProps)(Index);
You're missing in the last statement. As we don't have mapStateToProps, so the statement will be like below
const StartContainer = connect(null, mapDispatchToProps)(Start)
When you do not provide mapDispatchToProps as a second argument, like this:
export default connect(mapStateToProps)(Checkbox)
then you are automatically getting the dispatch to component's props, so you can just:
class SomeComp extends React.Component {
constructor(props, context) {
super(props, context);
}
componentDidMount() {
this.props.dispatch(ACTION GOES HERE);
}
....
without any mapDispatchToProps
i am using like this.. its easy to understand first argument is mapStateToProps and second argument is mapDispatchToProps in the end connect with function/class.
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
export default connect(mapStateToProps,mapDispatchToProps)(TodoList);
Sometime this error also occur when you change the order of Component Function while passing to connect.
Incorrect Order:
export default connect(mapDispatchToProps, mapStateToProps)(TodoList);
Correct Order:
export default connect(mapStateToProps,mapDispatchToProps)(TodoList);
I got this issue when i wrote :
export default connect (mapDispatchToProps,mapStateToProps)(SearchInsectsComponent);
instead of
export default connect (mapStateToProps,mapDispatchToProps)(SearchInsectsComponent);