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,
),
})
Related
When the state in Redux is updated, if it is different from the useState of the current page, the useState in the page will be updated, but the value in the red box will not change after the update, it is always the default true.
import { useEffect, useState } from "react";
import store from "./redux";
import { useDispatch } from "react-redux";
import { setState } from "./redux/modules/menu";
function App() {
const dispatch = useDispatch();
const [baseState, setBaseState] = useState(true);
useEffect(() => {
console.log("baseState :>> ", baseState);
}, [baseState]);
useEffect(() => {
const unSubscribe = store.subscribe(() => {
console.log(store.getState().menu.state, baseState);
if (store.getState().menu.state !== baseState) {
setBaseState(store.getState().menu.state);
}
});
return () => unSubscribe();
}, []);
const buttonEvent = () => {
const storeState = store.getState().menu.state;
dispatch(setState(!storeState));
};
return (
<div className="App">
<h1>value: {baseState + ""}</h1>
<button onClick={buttonEvent}>change</button>
</div>
);
}
export default App;
run result :
So if i got your question, what you are doing is, on a button click, you are changing the redux state and on that basis, you want to change the local state.
But in your useEffect() (2nd one);
useEffect(() => {
const unSubscribe = store.subscribe(() => {
console.log(store.getState().menu.state, baseState);
if (store.getState().menu.state !== baseState) {
setBaseState(store.getState().menu.state);
}
});
return () => unSubscribe();
}, []);
You are giving an empty array as 2nd argument, which means this useEffect is going to get triggered only on the first render(similar to componentDidMount in class components), thus will never know if the redux state has changed.
Thus, to make it working, just remove them (like we have componentDidUpdate in class components).
I found the most appropriate solution, using Redux useSelector
I'm using React + Redux and have a number of child components <IndividualValue/> that each load a value from an API when they mount. In addition they dispatch an action which accumulates the total of these values that is displayed in another component <TotalValues />. However you can't call dispatch inside componentDidMount.
I am able to achieve this using connect and mapDispatchToProps but what is the correct way to do this with Redux and Hooks?
Key parts of my code:
class IndividualValue extends React.Component {
constructor(props) {
super(props);
this.state = {value: 0}
const dispatch = useDispatch();
}
async componentDidMount() {
let value = axios.get(`api/{this.props.name}`).data.value;
this.setState({value: value})
dispatch(incrementTotalByValue(value)); // <--- ???
}
render() {
return (
<div>{this.props.name} = {this.state.value}</div>
)
}
}
function TotalValues() {
const total = useSelector((state) => state.counter.total)
return (
<div>
<div>
<span>{total}</span>
</div>
</div>
)
}
ReactDOM.render(
<Provider store={store}>
<IndividualValue name="nick" />
<IndividualValue name="oscar" />
<IndividualValue name="michael" />
<TotalValues />
</Provider>,
document.getElementById('root')
);
It seems that a way to achieve this is:
const [value, setValue] = useState(null);
const dispatch = useDispatch();
...
useEffect(() => {
(async () => {
let value = await axios.get(`api/{props.name}`).data.value;
setValue(value)
dispatch(incrementTotalByValue(value));
})();
}, [])
I don't know if that is the recommended way to do things with hooks. It appears useEffect can be used in a similar way to componentDidMount but there are differences and it's not a drop in replacement.
In Class Components, I usually do this:
import React, { Component } from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { Actions as NameActions } from "../../store/ducks/actionsHere";
class Main extends Component {
render() {
return <h1>Text</h1>
}
}
const mapStateToProps = (state) => ({
data: state.data,
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators(NameActions, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(Main);
I am using react-redux with redux and redux-toolkit. And according to this example, i created an async dispatch that calls the reducer action when resolved.
import { createSlice } from "#reduxjs/toolkit";
import axios from "axios";
export const BlogSlice = createSlice({
name: "Blog",
initialState: {
BlogList: null,
},
reducers: {
getBlogList: (state, action) => {
console.log(action.payload);
state.BlogList = action.payload;
}
},
});
export const { getBlogList } = BlogSlice.actions;
export const getBlogListAsync = (user_id) => (dispatch) => {
axios.get(`/api/blog/getblogs/${user_id}`).then((res) => {
console.log(res.data);
dispatch(getBlogList(res.data.result));
});
};
export const selectBlogList = (state) => state.Blog.BlogList;
export default BlogSlice.reducer;
I have used it in a component accordingly so that, the component dispatches getBlogListAsync and that logs the res.data but getBlogList is not being dispatched. I tried putting other console.log() but don't understand what is wrong.
A similar Slice is working perfectly with another Component.
It is hard to say for sure what's wrong here because there is nothing that is definitely wrong.
res.data.result?
You are logging res.data and then setting the blog list to res.data.result. My best guess as to your mistake is that res.data.result is not the the correct property for accessing the blogs, but I can't possibly know that without seeing your API.
console.log(res.data);
dispatch(getBlogList(res.data.result));
missing middleware?
Is there any chance that "thunk" middleware is not installed? If you are using Redux Toolkit and omitting the middleware entirely, then the thunk middleware will be installed by default. Also if this were the case you should be getting obvious errors, not just nothing happening.
it seems fine...
I tested out your code with a placeholder API and I was able to get it working properly. Maybe this code helps you identify the problem on your end. Code Sandbox Demo.
import React from "react";
import { createSlice, configureStore } from "#reduxjs/toolkit";
import axios from "axios";
import { Provider, useDispatch, useSelector } from "react-redux";
export const BlogSlice = createSlice({
name: "Blog",
initialState: {
BlogList: null
},
reducers: {
getBlogList: (state, action) => {
console.log(action.payload);
state.BlogList = action.payload;
}
}
});
export const { getBlogList } = BlogSlice.actions;
const store = configureStore({
reducer: {
Blog: BlogSlice.reducer
}
});
export const getBlogListAsync = (user_id) => (
dispatch: Dispatch
) => {
// your url `/api/blog/getblogs/${user_id}`
const url = `https://jsonplaceholder.typicode.com/posts?userId=${user_id}`; // placeholder URL
axios.get(url).then((res) => {
console.log(res.data);
// your list: res.data.result <-- double check this
const list = res.data; // placeholder list
dispatch(getBlogList(list));
});
};
export const selectBlogList = (state) => state.Blog.BlogList;
const Test = () => {
const dispatch = useDispatch();
const blogs = useSelector(selectBlogList);
const user_id = "1";
return (
<div>
<button onClick={() => dispatch(getBlogListAsync(user_id))}>
Load Blogs
</button>
<h3>Blog Data</h3>
<div>{JSON.stringify(blogs)}</div>
</div>
);
};
export default function App() {
return (
<Provider store={store}>
<Test />
</Provider>
);
}
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)
I am trying to get redux working in my react-native app. Basically, I have a signIn action defined in my authActions.js file:
const signInAction = () => {
return {
type: 'signIn',
};
};
export { signInAction };
Then I have an authReducer defined as this in authReducer.js:
const initialState = {
isAuthenticated: false,
}
const authReducer = (state = initialState, action) => {
switch(action.type) {
case "signIn":
return Object.assign({}, state, {
isAuthenticated: true,
})
default: return state;
}
};
export default authReducer;
I combine that reducer in my rootReducer.js file
import { combineReducers } from 'redux';
import auth from 'app/src/redux/reducers/authReducer.js';
const rootReducer = combineReducers({
auth,
});
export default rootReducer;
and then created a store in reduxIndex.js:
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import rootReducer from 'app/src/redux/reducers/rootReducer.js';
let store = createStore(rootReducer, applyMiddleware(thunkMiddleware));
export default store;
I wrapped my app in a <Provider> component, and that seems to be working fine (I can read from the state and see the value of isAuthenticated. However, when I try to dispatch an action using mapDispatchToProps in one of my views the function is undefined:
// More imports
// ...
import { connect } from 'react-redux';
import { signInAction } from 'app/src/redux/actions/authActions.js';
const mapStateToProps = (state) => {
return {};
}
const mapDispatchToProps = (dispatch) => {
return {
onSignIn: () => { dispatch(signInAction) },
};
}
class SignIn extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
}
}
onSignInPress() {
// ******* this is where the error occurrs ****
this.props.onSignIn();
}
render() {
const {navigation} = this.props;
return (
<View style={SignInStyles.container}>
<ScrollView>
<View>
<Button
large
title="SIGN IN"
backgroundColor={colors.primary}
onPress={this.onSignInPress}
/>
</View>
</ScrollView>
</View>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(SignIn);
I cant really see where I am going wrong, but im sure its a simple mistake somewhere. The specific error I get is :
"undefined is not an object. Evaluating this.props.onSignIn"
The onSignInPress callback isn't bound to any particular object, so when it gets called this is undefined.
The easy way to fix it is to use arrow syntax to make it always be bound. In your class definition:
onSignInPress = () => {
this.props.onSignIn();
}
Google found me this Medium article from Miron Machnicki which explains the differences and possible alternative syntaxes in pretty good detail.