bindActionCreators and mapDispatchToProps - Do I need them? - redux

I'm looking at a React-Redux app and try to understand how everything is working.
Inside one of the components, I saw these lines of code:
import { bindActionCreators } from "redux";
...
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchPhotos }, dispatch);
}
export default connect(
null,
mapDispatchToProps
)(SearchBar);
If I change the above code to the following, everything still works, without any errors:
function mapStateToProps(photos) {
return { photos };
}
export default connect(
mapStateToProps,
{ fetchPhotos }
)(SearchBar);
To me, it seems that my way of using connect is easier to understand and it also doesn't need to import an extra library.
Is there any reasons, to import bindActionCreators and use mapDispatchToProps?

I'm a Redux maintainer.
Yes, the second example you showed uses the "object shorthand" form of mapDispatch.
We recommend always using the “object shorthand” form of mapDispatch, unless you have a specific reason to customize the dispatching behavior.

I personally avoid using bindActionCreators explicitly. I prefer to directly dispatch the functions with mapDispatchToProps which internally uses bindActionCreators.
const mapStateToProps = state => ({
photos: state.photos.photos
});
const mapDispatchToProps = dispatch => ({
fetchPhotos: () => dispatch(fetchPhotos())
// ...Other actions from other files
});
export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);
There are two cases in which you'll use bindActionCreators explicitly, both are not best practices:
If you have a child component to SearchBar that does not connect to redux, but you want to pass down action dispatches as props to it, you can use bindActionCreators.
Best practice would be doing same with example I. You can just pass this.props.fetchPhotos to childcomponent directly without using bindActionCreators.
class SearchBar extends React.Component {
render() {
return (
<React.Fragment>
<ChildComponentOfSearchBar fetchPhotos={this.props.fetchPhotos} />
</React.Fragment>
)
}
}
const mapStateToProps = state => ({
photos: state.photos.photos
});
const mapDispatchToProps = () => bindActionCreators({ fetchPhotos }, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);
There is another unlikely scenario where you can use bindActionCreators, defining actionCreator inside the component. This isn't maintainable & is not a good solution since action types are hard coded and not reusable.
class SearchBar extends React.Component {
constructor(props) {
super(props);
this.fetchPhotosAction = bindActionCreators({ fetchPhotos: this.searchFunction }, dispatch);
}
searchFunction = (text) => {
return {
type: ‘SEARCH_ACTION’,
text
}
}
render() {
return (
<React.Fragment>
// Importing selectively
<ChildComponentOfSearchBar fetchPhotos={this.fetchPhotosAction} />
</React.Fragment>
)
}
}
const mapStateToProps = state => ({
photos: state.photos.photos
});
export default connect(mapStateToProps, null)(SearchBar)

Related

Need help for my first steps with React-Redux

I am struggeling on getting my first steps with Redux. All of these "Todo-App" Tutorials are nice, the "Increment the button" tutorials as well. I thought of getting my own example to teach myself the logic of Redux, but something doesnt work. At the moment, I am not sure where the state comes from, so I tried a lot of different variations to have Redux "started" without getting initialization errors, and I found a working solution! First, I just setted up the state in the reducer, but the button-describtion didnt appear. Then, I setted up the state in the store additionally, and at least the button has the decribtion test123 and the console.log worked. But how to get the state from the reducer (I checked the documentation and it was recommended to pass state by reducers, not by the store itself). At the moment, I get the following error:
Error: Objects are not valid as a React child (found: object with keys {0, 1, 2, 3}). If you meant to render a collection of children, use an array instead.
Here is my absolutely basic code which should help me understand the logic of redux:
Action type:
export const CLICK = 'CLICK'
Action Creator:
import { CLICK } from './types';
export function clicked() {
return({
type: CLICK,
payload: 'switch the describtion of the button'
})
}
the clickReducer:
import { CLICK } from '../actions/types';
const initialState = {
name: 'test'
}
export default function (state = initialState, action) {
console.log('click-test', action)
switch(action.type) {
case CLICK: {
return Object.assign({}, state)
}
default:
return state
}
}
the rootReducer:
import { combineReducers } from 'redux';
import clickReducer from './clickReducer';
export default combineReducers({
name: clickReducer
})
the store:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {
name: 'test123'
};
const middleWare = [thunk];
const store = createStore(rootReducer, initialState, applyMiddleware(...middleWare));
export default store;
and the button-component:
import React, { Component } from 'react'
import { connect } from 'react-redux';
import { clicked } from '../actions/clickAction';
class Button extends Component {
render() {
return (
<div>
<button onClick={this.props.clicked}>{this.props.name}</button>
</div>
)
}
}
const mapStateToProps = state => ({
name: state.name
});
export default connect(mapStateToProps, { clicked })(Button);
It would be very nice to get some help with this issue to be able to take further steps in Redux.
Thank you!
You don't need parentheses, do this instead:
import { CLICK } from './types';
export clicked = () => {
return {
type: CLICK,
payload: 'switch the describtion of the button'
}
}
Your "CLICK" type in the switch statement isn't updating the name, you're just returning the state. Do this, instead:
import { CLICK } from '../actions/types';
const initialState = {
name: 'test'
}
export default (state = initialState, action) => {
switch(action.type) {
case CLICK:
return {
...state,
name: action.payload
}
default:
return state
}
}
Your store has too much information, do this instead:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
export default store;
Call the object property:
import React, { Component } from 'react'
import { connect } from 'react-redux';
import { clicked } from '../actions/clickAction';
class Button extends Component {
render() {
return (
<div>
<button onClick={this.props.clicked}>{this.props.name.name}</button>
</div>
)
}
}
const mapStateToProps = state => ({
name: state.name
});
export default connect(mapStateToProps, { clicked })(Button);
this solution, regarding the reducer, still leads to the following error:
Error: Objects are not valid as a React child (found: object with keys {0, 1, 2, 3}). If you meant to render a collection of children, use an array instead.
in button (at button.js:9)
in div (at button.js:8)
in Button (created by ConnectFunction)
in ConnectFunction (at App.js:12)
in div (at App.js:11)
in Provider (at App.js:10)
in App (at src/index.js:6) react-dom.development.js:57
React 15
dispatchInteractiveEvent self-hosted:1029
I really cant imagine why it is like this, because my solution looks like okay and this is a very very primitive app to change the button description :(((

How to use mapDispatchToProps

I am learning Redux React. How to use mapDispatchToProps ? My code is like below
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getAddress } from '../store/actions/addressActions';
class Dashboard extends Component {
componentDidMount = () => {
this.props.getAddress();
};
render() {
return <div>Hello { console.log(this.props) } </div>;
}
}
const mapStateToProps = state => ({
address: state.address
});
const mapDispatchToProps = dispatch => ({
getAddress
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Dashboard);
I am getting console output like below
Where I am doing mistake ? How to use mapDispatchToProps properly ?
Either you define mapDispatchToProps as an object or you return the dispatched function from mapDispatchToProps instead of an object
Using first approach your mapStateToProps will look like
const mapDispatchToProps = {
getAddress
};
Using second approach it would look like
const mapDispatchToProps = dispatch => ({
getAddress: (...args) => dispatch(getAddress(...args));
});
Also since you are using combineReducers you need to change how you access the state in mapStateToProps
const mapStateToProps = state => ({ address: state.addressReducer.address });
I think, initially in store the address is undefined. correct? and the getAddress action will set the address value.
Here is what you missed,
1) you have dispatch the action. you can use any of following
const mapDispatchToProps = dispatch => ({
getAddress: (...args) => dispatch(getAddress(...args));
});
or
import { bindActionCreators } from "redux";
const mapDispatchToProps = dispatch => bindActionCreators({getAddress}, dispatch)
Your should update mapDispatchToProps method a little bit. In addition, you should export it to writing test.
import { bindActionCreators } from 'redux';
export const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(
getAddress,
dispatch,
),
})

TypeError: Object(...) is not a function (redux)

I am having a TypeError (TypeError: Object(...) is not a function) when I want to dispatch an action. I'm not using any middleware and don't know what I can do to solve it. I had this error already yesterday but somehow managed to solve it (i donk know how i did this)
This is the App.js:
import React from "react";
import { store } from "../store";
import { withdrawMoney} from "../actions";
const App = () => {
return (
<div className="app">
<div className="card">
<div className="card-header">
Welcome to your bank account
</div>
<div className="card-body">
<h1>Hello, {store.getState().name}!</h1>
<ul className="list-group">
<li className="list-group-item">
<h4>Your total amount:</h4>
{store.getState().balance}
</li>
</ul>
<button className="btn btn-primary card-link" data-amount="5000" onClick={dispatchBtnAction}>Withdraw $5,000</button>
<button className="btn btn-primary card-link" data-amount="10000" onClick={dispatchBtnAction}>Witdhraw $10,000</button>
</div>
</div>
</div>
);
}
function dispatchBtnAction(e) {
store.dispatch(withdrawMoney(e.target.dataset.amount));
}
export default App;
Here is the actioncreator:
function withdrawMoney(amount) {
return {
type: "ADD_TODO",
amount
}
}
If you need here is the reducer:
export default (state, action) => {
console.log(action);
return state
}
As you can see I'm a very new to redux but I'd like to know what mistake I make all the time when dispatching an action. Thanks
I believe the issue is that you aren't exporting the withdrawMoney function, so you aren't able to call it in the component that you're attempting to import into.
try:
export function withdrawMoney(amount) {
return {
type: "ADD_TODO",
amount
}
}
Another subtle mistake that will cause this error is what I tried to do, don't accidentally do this:
import React, { useSelector, useState ... } from 'react'
it should be:
import React, { useState } from 'react'
import { useSelector } from 'react-redux'
Try to install :
npm i react#next react-dom#next and run again
const mapStateToProps = state => ({
Bank: state.Bank,
});
function mapDispatchToProps(dispatch) {
return {
dispatch,
...bindActionCreators({ getBanks, addBank }, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(BankComponent);
this worked like a Charm for me .
bit Late to party,
to me it was about casing.
export const addTodo = text => ({
type: ADD_TODO,
desc: text
});
export const removeTodo = id=> ({
type: REMOVE_TODO,
id: id
})
export const increaseCount = () => ({
type: INCREASE_COUNT
});
export const decreaseCount = () => ({
type: DECREASE_COUNT
})
when I renamed all those like
export const AddTodo = text => ({
type: ADD_TODO,
desc: text
});
export const RemoveTodo = id => ({
type: REMOVE_TODO,
id: id
})
export const IncreaseCount = () => ({
type: INCREASE_COUNT
});
export const DecreaseCount = () => ({
type: DECREASE_COUNT
})
it worked.
I spent hours debugging this, turns out I was doing this:
import React, { connect } from "react";
Instead of
import React from "react";
import { connect } from "react-redux";
It's weird the former didn't throw an error, but it will cause the problem!
First problem I see right off the bat is your reducer is not setup to do anything with the dispatched withdrawMoney action creator.
export default (state, action) => {
switch (action.type) {
case "ADD_TODO": {
return {
...state,
amount: action.amount,
};
}
default:
return state;
}
};
If this does not help, the code from your other files would be helpful.
It may not be the main issue, but it looks like you're not using the React-Redux library to work with the store. You can reference the store directly in your React components, but it's a bad practice. See my explanation for why you should be using React-Redux instead of writing "manual" store handling logic.
After updating react-redux to version 7.2.0+ I was receiving this error anytime I wrote:
const dispatch = useDispatch()
I stopped my application and re-ran it with npm start, and everything is working now.
I was using redux toolkit and for me the problem was an extra '}'
such that my 'reducers' object, in 'createSlice' function, already had a closing curly brace before my second reducer
and my 2nd reducer was actually outside the 'reducers' object,
making it not a reducer and hence not working even when you export or import it properly.
So the problem is not in your export or import, but actually where your function is defined.
This may be different for other users, but in my case this turned out to be the cause of this error.

Can I always use this.props to call a function from the actions?

I'm doing a Redux tutorial, and don't understand something in it. I have the following container:
import React, { Component } from 'react';
import CommentsList from "./comments_list";
import { connect } from 'react-redux';
import * as actions from '../actions';
class CommentBox extends Component {
constructor(props) {
super(props);
this.state = { comment: '' };
}
handleChange(event) {
this.setState({ comment: event.target.value })
}
submitButton(e) {
e.preventDefault();
this.props.saveComment(this.state.comment);
this.setState({ comment: '' });
}
render () {
return(
<div>
<form onSubmit={(e) => this.submitButton(e)} className="comment-box">
<textarea
value={this.state.comment}
onChange={(e) => this.handleChange(e)} />
<button action="submit">Submit</button>
</form>
<CommentsList comment={this.state.comment}/>
</div>
);
}
}
export default connect(null, actions)(CommentBox);
This container uses:
import * as actions from '../actions';
and on the bottom of the file:
export default connect(null, actions)(CommentBox);
I'm used to using mapStateToProps and mapDispatchToProps, but here only the actions are imported, and then used in the submitButton(e) method:
this.props.saveComment(this.state.comment);
The saveComment comes from the actions/index.js file:
import { SAVE_COMMENT } from './types';
export function saveComment(comment) {
return {
type: SAVE_COMMENT,
payload: comment
}
}
Can I always use this.props to call a function from the actions/index.js file? Why don't I need to use the mapStateToProps first?
Can I always use this.props to call a function from the actions/index.js file?
Yes. From the react-redux docs:
[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function): If an object is passed, each function inside it is assumed to be a Redux action creator. An object with the same function names, but with every action creator wrapped into a dispatch call so they may be invoked directly, will be merged into the component’s props.
connect is wrapping the exports from actions/index.js with dispatch calls for you.
Why don't I need to use the mapStateToProps first?
Because mapStateToProps and mapDispatchToProps are used for different purposes and mapped separately before being merged together and injected into your component.
If either return undefined or null, they are ignored. In the case of mapStateToProps, it also means the component won't subscribe to updates from the store. Again, from the react-redux docs:
If you don't want to subscribe to store updates, pass null or undefined in place of mapStateToProps.

REDUX: Why won't the store provide data to my component?

Newbie here trying to learn some Redux.
GOAL: to get a button to click and login/logout, updating the store as true/false status whichever way.
const store = createStore(myReducer)
Created my store, passing in my reducer.
This has a default state of logged out. And returns the opposite, whenever the button is clicked.
I know this action works through debugging.
function myReducer(state = { isLoggedIn: false }, action) {
switch (action.type) {
case 'TOGGLE':
return {
isLoggedIn: !state.isLoggedIn
}
default:
return state
}
}
The problem starts here - when i try to access the store.getState() data.
class Main extends React.Component {
render() {
return (
<div>
<h1>Login Status: { state.isLoggedIn }</h1>
<button onClick={this.props.login}>Login</button>
</div>
)
}
}
const render = () => {
ReactDOM.render(<Main status={store.getState().isLoggedIn} login={() => store.dispatch({ type: 'TOGGLE' })}/>, document.getElementById('root'));
}
store.subscribe(render);
render();
I've tried store.getState().isLoggedIn & store.getState() & this.props.status and then assigning the store.getState().isLoggedIn in the Main component - but nothing works.
Can anyone tell me where i'm going wrong?
You don't directly access the store using getState to find data. The Redux docs explain the process in-depth, but basically you'll connect each component to the Redux store using connect method of the react-redux package.
Here's an example of how this could work for your above component:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import Main from '../components/Main'
class MainContainer extends Component {
render() {
return <Main {...this.props} />
}
}
const mapStateToProps = state => ({
isLoggedIn: state.isLoggedIn,
})
const mapDispatchToProps = dispatch => ({
login() {
dispatch({type: 'TOGGLE'})
},
})
MainContainer = connect(
mapStateToProps,
mapDispatchToProps,
)(MainContainer)
export default MainContainer
You would then want to render the MainContainer in place of the Main component. The container will pass down isLoggedIn and login as props to Main when it renders it.

Resources