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);
Related
I was learning React and Redux and while doing that I decided to make webpage with a button which on clicking would change the state. Below the button I wanted to display the current state in a different component. Though the button on clicking changes the state, but it is not getting reflected in the component. Here is my code:
App.js
import React from 'react'
import Name from './Name'
import {changeName} from './Action';
export default function App () {
return (
<div>
<button onClick={changeName}>Click me</button>
<Name />
</div>
)
}
Name.js
import React from 'react'
import {store} from './Store'
function Name(props) {
return (
<div>
My name is: {store.getState()}
</div>
)
}
export default Name
Store.js
import { createStore } from 'redux';
import {reducer} from './Reducer';
export const store = createStore(reducer, 'Tarun');
Action.js
import {store} from './Store';
export const changeName = () => {
if (store.getState() === "Tarun"){
store.dispatch({ type: 'name', payload: 'Subhash' });
}
else{
store.dispatch({ type: 'name', payload: 'Tarun' });
}
}
Reducer.js
export const reducer = function(state, action) {
if (action.type === 'name') {
return action.payload;
}
return state;
};
When I click the button, The text inside the Name component does not change. What is the issue?
You need to set up your reducer and initial store properly following the Redux documentation.
You're missing a Provider, which will provide your store to your application.
const store = createStore(reducer, applyMiddleware(thunk));
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
Now, your store is available to your components.
Your reducer needs an initial state too and you're always supposed to return an updated copy of your state. That said, don't change the state directly, but make a copy, change it, then return that copy.
const initialState = {
name: ""
};
const reducer = function(state = initialState, action) {
if (action.type === "name") {
return { ...state, name: action.payload };
} else {
return state;
}
};
export default reducer;
You might have noticed that I added a middleware to your store, and that's because it's usually the way to go when accessing your current reducer's state in your actions. That said, I installed redux-thunk for that, so in your action, you can have something like this:
export const changeName = () => {
return (dispatch, getState) => {
if (getState().name === "Tarun") {
dispatch({ type: "name", payload: "Subhash" });
} else {
dispatch({ type: "name", payload: "Tarun" });
}
};
};
Now, with your store being provided to your app, your reducer being done and your actions being ready to go, you can connect different components to your reducer.
You use the high order component in react-redux called connect for that. For example, in your Name component, we can connect the name to be displayed to your reducer by mapping your state to the component's props:
function Name(props) {
return <div>My name is: {props.name}</div>;
}
const mapStateToProps = state => {
return {
name: state.name
};
};
export default connect(mapStateToProps)(Name);
The nice thing here is that you can also leave the first parameter in the connect high order component empty and just pass the second, which would be the dispatch functions. Well, that's what you would do in your App component, you would connect it to the changeName action.
function App(props) {
return (
<div>
<button onClick={props.changeName}>Click me</button>
<Name />
</div>
);
}
const mapDispatchToProps = dispatch => {
return {
changeName: () => dispatch(changeName())
};
};
export default connect(
null,
mapDispatchToProps
)(App);
Now, when App dispatches a changeName action, your reducer state will be updated and the other components that are connected to the reducer's state will re-render.
Summary: Try to think of your store as an empty jar of candies. Your jar starts empty, but different actions could change what's inside the jar. On top of that, different people in the house that know where the jar is can go get some candy. Translating to your problem, your app begins with an empty name and you have an action that sets up a name. The components that know where to find that name by being connected to your reducer will know when that name changes and will get the updated name.
The final code can be found here:
The only way your name component will rerender is its props or state change, or if a parent component rerenders. Making a change in redux will not automatically do this. In order to see changes to the state, you'd need to subscribe to those changes. You could do this yourself, but a far better solution is to use react-redux, which is designed for connecting react components to redux stores.
For example, you'd add a provider to your app:
import { Provider } from 'react-redux';
import { store } from './Store'
export default function App () {
return (
<Provider store={store}>
<div>
<button onClick={changeName}>Click me</button>
<Name />
</div>
</Provider>
)
}
And then you'd use connect with your Name component:
import { connect } from 'react-redux';
function Name(props) {
return (
<div>
My name is: {props.name}
</div>
)
}
const mapStateToProps = (state) => {
return { name: state };
}
export default connect(mapStateToProps)(Name)
In my React + Redux application I am trying to use mapDispatchToProps utility, But whenever I put this inside connect(mapStateToProps, mapDispatchToProps) it gives me an error saying Uncaught TypeError: dispatch is not a function at new ReduxApp (ReduxApp.js:42)
What could be the issue in this?
PS: below is the file
ReduxApp.js
import React from 'react';
import { Router, Route } from 'react-router-dom';
import { connect } from 'react-redux';
import { history } from './_helpers';
import { alertActions } from './_actions'
class ReduxApp extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
const { dispatch } = this.props;
dispatch(alertActions.success("hello world"));
}
handleChange(){
this.props.dispatch(alertActions.clear());
}
render(){
const { alert } = this.props;
return(
<div>
<h1>{alert.message}</h1>
<button onClick={this.handleChange}>clear</button> {/* this is working since this function is declared outside the mapDispatchToProps. */}
<button onClick={this.props.handleClick}>clear</button>
</div>
);
}
}
const mapStateToProps = (state) => ({
alert : state.alert
});
const mapDispatchToProps = (dispatch) => ({
handleClick: () => dispatch(alertActions.clear())
});
const connectedApp = connect(mapStateToProps, mapDispatchToProps)(ReduxApp); // when I add mapDispatchToProps in the connect(), I get thhe issue.
export { connectedApp as ReduxApp }
you first need to pass dispatch as it is not available when using mapDispatchToProps (see this answer by #gaeron Redux's creator: https://github.com/reduxjs/react-redux/issues/255)
const mapDispatchToProps = dispatch => ({
handleClick: () => alertActions.clear(dispatch),
dispatch,
});
Update your actionCreator to dispatch the action now that dispatch's reference is available:
alert.clear = dispatch => {
// your logic
dispatch(ALERT_CLEAR_ACTION) // or whatever you named your action
}
And in your component:
handleChange = () => this.props.handleClick();
From React Redux Official Documentation
Why don't I have this.props.dispatch available in my connected component?
The connect() function takes two primary arguments, both optional. The first, mapStateToProps, is a function you provide to pull data from the store when it changes, and pass those values as props to your component. The second, mapDispatchToProps, is a function you provide to make use of the store's dispatch function, usually by creating pre-bound versions of action creators that will automatically dispatch their actions as soon as they are called.
If you do not provide your own mapDispatchToProps function when calling connect(), React Redux will provide a default version, which simply returns the dispatch function as a prop. That means that if you do provide your own function, dispatch is not automatically provided. If you still want it available as a prop, you need to explicitly return it yourself in your mapDispatchToProps implementation.
The issue got solved after returning dispatch in the mapDispatchToProps implementation
const mapDispatchToProps = (dispatch) => ({
handleClick: () => dispatch(alertActions.clear()),
dispatch, //returning dispatch solves the issue
});
Note: If we use PropTypes no need to retun mapDispatchToProps
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'm learning redux and react. I am following some tutorials, in order to make a app.
I have this action:
export function getDueDates(){
return {
type: 'getDueDate',
todo
}
}
this is the store:
import { createStore } from 'redux';
import duedates from './reducers/duedates'
export default createStore(duedates)
This is the reducer:
import Immutable from 'immutable'
export default (state = Immutable.List(['Code More!']), action) => {
switch(action.type) {
case 'getDueDate':
return state.unshift(action.todo)
default:
return state
}
}
and in the entry point js I have this:
import React from 'react';
import ReactDOM from 'react-dom';
import store from './app/store'
import { Provider } from 'react-redux'
import App from './app/Components/AppComponent';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
);
Now, (according to some examples), I should call getDueDate from the dispatch but I dont get how to get the dispatch on the component, to trigger the action
Use connect from react-redux package. It has two functions as params, mapStateToProps and mapDispatchToProps, which you are interested in now. As per answer from Nick Ball, which is partially right, you will be exporting like this:
export default connect(mapStateToProps, mapDispatchToProps)(App)
and your mapDispatchToProps will look something like this:
function mapDispatchToProps (dispatch, ownProps) {
return {
getDueDate: dispatch(getDueDate(ownProps.id))
}
}
as long as your component connected to the store has property id passed from above, you'll be able to call this.props.getDueDate() from inside of it.
EDIT: There is probably no need of using an id in this case, however my point was to point out that props go as second parameter :)
The missing piece here is the connect function from react-redux. This function will "connect" your component to the store, giving it the dispatch method. There are variations on how exactly to do this, so I suggest reading the documentation, but a simple way would be something like this:
// app/Components/AppComponent.js
import { connect } from 'react-redux';
export class App extends React.Component {
/* ...you regular class stuff */
render() {
// todos are available as props here from the `mapStateToProps`
const { todos, dispatch } = this.props;
return <div> /* ... */ </div>;
}
}
function mapStateToProps(state) {
return {
todos: state.todos
};
}
// The default export is now the "connected" component
// You'll be provided the dispatch method as a prop
export default connect(mapStateToProps)(App);