bindActionCreators called on a dispatch - javascript

I was under the impression that the purpose of bindActionCreators was to wrap actionCreators in a dispatch function, and give these new functions a name (which could be passed to components as props, via mapDispatchToProps and connect).
However, a tutorial I found seemed to call bindActionCreators on a dispatch function (updatePerson), which seems to violate the whole point of bindActionCreators.
actions/update_person.js
import { UPDATE_PERSON } from './types';
export default function updatePerson(person) {
return dispatch => {
dispatch(updatePersonAsync(person));
}
}
function updatePersonAsync(person){
return {
type: UPDATE_PERSON,
payload: person
};
}
components/WantedCard.js
//connects redux actions to props
function mapDispatchToProps(dispatch) {
return bindActionCreators({
updatePerson: updatePerson,
deletePerson: deletePerson
}, dispatch);
}
What exactly am I getting wrong here, with my understanding? UpdatePerson is already bound(?)
This is the tutorial repo: https://github.com/lorenseanstewart/redux-wanted-list and blog post https://lorenstewart.me/2016/11/27/a-practical-guide-to-redux/

UpdatePerson is already bound(?)
No it wasn't it's just a normal function that you import.
In order to make it play with the redux flow cycle you need to dispatch this function.
When you are not passing mapDispatchToProps you get the dispatch function as a prop to your connected component, so to use it you would have to do it like this:
this.props.dispatch(updatePerson())
If you do decide to pass mapDispatchToProps to connect then you will not receive dispatch as a prop but you could wrap your actions with it:
const mapDispatchToProps = dispatch => {
return {
updatePerson: () => {
dispatch(updatePerson())
}
}
}
Or you can just pass an object:
const mapDispatchToProps = {
updatePerson,
deletePerson
}
Another way is to use bindActionCreators (like you mentioned in your post)
With this approach you can dispatch the action with this line of code:
function mapDispatchToProps(dispatch) {
return bindActionCreators({
updatePerson: updatePerson,
deletePerson: deletePerson
}, dispatch);
}
And call it like this:
this.props.updatePerson()
Note that you can use the Shorthand property names of ES2015 if the key matches the variable
function mapDispatchToProps(dispatch) {
return bindActionCreators({
updatePerson,
deletePerson
}, dispatch);
}
Another nice approach with bindActionCreators is to import all actions as alias (even different actions from different files):
import * as userActions from '../url';
import * as otherActions from '../otherUrl';
And then stack them all in one object (or separate them if you want):
function mapDispatchToProps(dispatch) {
const combinedActions = { ...userActions, ...otherActions };
return {
actions: bindActionCreators(combinedActions, dispatch)
};
}
Now you can refer any action via the actions object:
this.props.actions.myAction();
this.props.actions.myOtherAction();
You can read me about the various options in the docs

Related

I am not able to use mapDispatchToProps in my React + Redux application

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

Difficulties when try to map the Redux state with the props of the container

I am trying to get familiar with the flow of the react-boilerplate.
Till now I love how neat clean and easy to understand are things, I although feel that I miss a piece of the puzzle. Would be nice if someone with more experience could help me with that.
The problem I am facing at the moment goes as follows.
I am triggering an action within componentWillMount() of a specific component.
The action is being created in actions.js, its a simple get request made with axios.
The data are being processed in a promise middleware library redux-promise.
The promise is now being passed into the reducer of the specific component, where the whole state and the data that I need are being returned.
Trying to catch this state at the component is where I fail. I am trying to mapStateToProps but cannot find the data that I need there instead a Map {} is being received.
How do I Map this object with my props ?
I am sure I miss something important.
Here is my repo.
https://github.com/paschalidi/blog-react-redux
And here is my code so you can have a brief look.
index.js
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'
import { fetchPosts } from './actions'
import selectPostsIndex from './selectors'
export class PostsIndex extends React.Component { // eslint-disable-line react/prefer-stateless-function
componentWillMount() {
this.props.fetchPosts();
}
render() {
return (
<div>
<h3>Posts</h3>
<ul className="list-group">
A list would render here.
</ul>
</div>
);
}
}
function mapStateToProps(state) {
console.log(state.posts)
//return { posts: state } //****I dont get why the redux state is not being given here.
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchPosts }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(PostsIndex);
actions.js
import axios from 'axios'
import { FETCH_POSTS } from './constants';
const ROOT_URL = 'http://reduxblog.herokuapp.com/api';
const API_KEY = '?key=dsklhfksdhfjkdshfkjdshkj';
export function fetchPosts() {
const request = axios.get(`${ROOT_URL}/posts${API_KEY}`);
return {
type: FETCH_POSTS,
payload: request
};
}
store.js
import promise from 'redux-promise';
const middlewares = [
sagaMiddleware,
routerMiddleware(history),
promise
];
reducer.js
import { fromJS } from 'immutable';
import {
FETCH_POSTS
} from './constants';
const initialState = fromJS({ all:[], post: null });
function postsIndexReducer(state = initialState, action) {
switch (action.type) {
case FETCH_POSTS:
return { ...state, all: action.payload.data };
default:
return state;
}
}
export default postsIndexReducer;
Also the action is being registered in reducers.js
import PostsReducer from 'containers/PostsIndex/reducer'
export default function createReducer(asyncReducers) {
return combineReducers({
route: routeReducer,
language: languageProviderReducer,
posts: PostsReducer,
form: reduxFormReducer,
...asyncReducers,
});
}
Note I didn't test your code, but it looks like your reducer puts the fetched data in the field all of your global states posts field, but your mapStateToProps doesn't pick that up. Note that mapStateToProps should slice the part of the global state that the given component is interested in.
After a successful fetch the state you receive in mapStateToProps should look something like this:
{
posts: {
all: // whatever fetch returned
post: null
}
}
So your mapStateToProps could look something like this (note that this method receives the global state as an argument, not just for the specific reducer):
function mapStateToProps(state) {
// in component this.props.posts is { all: /* fetch result */, post: null }
return { posts: state.posts }
}
Also try to debug these methods, it becomes clearer once you see the flow of the data!
This GitHub issue covers this exact problem: https://github.com/reactjs/react-redux/issues/60.
I had to manually extract the values from the Map in mapStateToProps function:
const mapStateToProps = (state) => {
return {
posts: state.get('posts'),
};
}
Thanks to this StackOverflow post.

React use redux action don't work In own module

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.

Redux: Are only synchronous calls allowed from reducer functions?

I have a reactJs app and right now I'm learning Redux to use it as Flux implementation.
I've created a store and I have created my first reducer function but now I have some questions that come to my mind, please help me to understand.
As you can see I have an action called 'FIND_PRODUCTS' which is basically fetching data from a backend service. To call this backend service I use basically an asynchronous ajax call, so basically the problem I'm facing is that the state is returned from the reducer function before my backend call has finished, so then the state is not updated correctly and the subscribers to the store are getting incorrect data. This problem is solved if I switch to a synchronous call, but then, the first warning I get is that synchronous call should be avoided because it might decrease the user's experience (performance).
So my question, can we only fetch data synchronously from a reducer function?
Should the fetching data happens in the reducer function or there is another way to do that? if so, what is it?
Does this model of redux of having a single object tree to maintain the state scales well with large applications? If I have 1000 actions the switch in my reducer function will be huge! How can we avoid that?
Thank you!!
const initialState = {
availableLocales: [{text: 'En'}, {text: 'Es'}, {text: 'Fr'}],
selectedLocale: 'En',
translations: i18n.getTranslations(),
products: []
};
const reducer = (state = initialState, action = {type: 'NONE'})=> {
//To make the reducer a pure function
deepFreeze(state);
deepFreeze(action);
switch (action.type) {
case 'SWITCH_LOCALE':
let newState = Object.assign({}, state, {
selectedLocale: action.locale,
translations: i18n.getTranslations(action.locale)
});
return newState;
case 'FIND_PRODUCTS':
let newState = Object.assign({}, state, {
products:ProductHelper().findProductsByProductType(action.productType)
});
return newState;
default:
return state
}
return state;
}
// Create a Redux store holding the state of your app.
// Its API is { subscribe, dispatch, getState }.
const store = createStore(reducer);
// You can subscribe to the updates manually, or use bindings to your view layer.
store.subscribe(() =>
console.log(store.getState())
);
export default store;
Consider this:
Create actions.js file and export the actions functions like this:
import * as types from '../constants/action_types';
import * as api from '../utils/api'
export function something1(someId){
return (dispatch) => {
dispatch({type: `${types.SOMETHING1}_PENDING`});
api.getSomething(someId)
.then((res) => {
dispatch({
type: `${types.SOMETHING1}_SUCCEEDED`,
somethings: res.body
});
.catch((err) => {
dispatch({
type: `${types.SOMETHING1}_FAILED`,
errors: err.body
})
});
}
}
export function something2(someOtherId){
return (dispatch) => {
dispatch({type: `${types.SOMETHING2}_PENDING`});
api.getSomething2(someOtherId)
.then((res) => {
dispatch({
type: `${types.SOMETHING2}_SUCCEEDED`,
otherThings: res.body
});
.catch((err) => {
dispatch({
type: `${types.SOMETHING2}_FAILED`,
errors: err.body
})
});
}
}
Then the state only change when you have the data
Next separate your reducers in separate files and create one file to export them all
like this reducers/index.js:
export { default as reducer1 } from './reducer1';
export { default as reducer2 } from './reducer2';
export { default as reducer3 } from './reducer3';
export { default as reducer4 } from './reducer4';
Then config your store like this:
configure_store.js
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import * as reducers from '../reducers';
const rootReducer = combineReducers(reducers);
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
export default function configureStore(initialState) {
return createStoreWithMiddleware(rootReducer, initialState);
}
Finally add this to your root:
import configureStore from '../store/configure_store';
const store = configureStore();
class Root extends Component {
render() {
return (
...
<Provider store={ store } >
...
</Provider>
);
}
}
export default Root;
First, you CAN'T fetch data in reducer, because it needs to be pure by redux definition. You should create action creator, that would fetch data asynchronously and pass it to reducer. Actions CAN be impure.
Here you can read more http://redux.js.org/docs/advanced/AsyncActions.html
Also you can use middleware like redux-thunk to simplify this. https://github.com/gaearon/redux-thunk
As for the second question, you can have more than one reducer in your app. and than combine them with combineReducers(...) function http://redux.js.org/docs/basics/Reducers.html
As redux documentation said, reducers should be pure functions, so it shouldn't do ajax requests.
Better way to do so is use redux-thunk middleware, that allows you to call dispatch several times in one action.
So, in your example you do something like this:
// definition of action creator
function loadProducts(productType) {
return {type: 'FIND_PRODUCTS', productType: productType}
}
...
// calling dispatch of your action
dispatch(loadProducts(productType));
But with redux-thunk your action creator will be something like this:
function loadProducts(productType) {
return function(dispatch){
dispatch({type: 'FIND_PRODUCT_STARTED'});
// I don'h know how findProductsByProductType works, but I assume it returns Promise
ProductHelper().findProductsByProductType(productType).then(function(products){
dispatch({type: 'FIND_PRODUCT_DONE', products: products});
});
}
}
And your reducer will become pure function:
...
case 'FIND_PRODUCTS_DONE':
let newState = Object.assign({}, state, {
products: action.products,
});
return newState;
...
In this case you can also handle loading state, i.e. set loading flag in your state to true when action.type is FIND_PRODUCT_STARTED.
In my example I assume that findProductsByProductType returns Promise. In this case you can even use redux-promise-middleware without redux-thunk, it will do all work for you:
function loadProducts(productType) {
return {
type: 'FIND_PRODUCT',
payload: {
promise: ProductHelper().findProductsByProductType(productType)
}
}
}
You should not use ProductHelper() in your reducer to request data.
Instead, you should use an action creator to dispatch an action that requests the data from your API. Your API middleware would return a promise that on completion would dispatch an action intent with payload for your reducer to consume and for it to return the next state.
I recommend you look at Redux Thunk and Redux API middleware

'dispatch' is not a function when argument to mapToDispatchToProps() in Redux

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);

Categories

Resources