I've been trying to figure this out for a few hours now and can't make headway.
Question: I'm getting an error "Cannot access before initialization" on this line:
const mapDispatchToProps = { changeGreen };
I'm exporting my action creator from index like this:
export const changeGreen = () => {
return {
type: GREEN_ON
};
};
I'm importing into MyComponent.js like so:
import { changeGreen } from "./index";
I'm mapping to dispatch in the component file like so:
const mapDispatchToProps = { changeGreen };
export default connect(
mapStateToProps,
mapDispatchToProps
)(MyComponent);
My App.js looks like:
function App() {
return (
<div className='App'>
<TrafficLight />
</div>
);
}
I'm wrapping the App with Provider like so:
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Any help appreciated.
Figured it out, I wasn't initializing the action creator before the render statement.
Fixed it by adding this before the render statement
changeGreen = () => {
this.props.changeGreen();
};
You need to map your action to props and dispatch, it should look like the below
const mapDispatchToProps = (dispatch: any) => {
return {
changeGreen : bindActionCreators<any, any>(changeGreen , dispatch)
}
}
Then you'd call it as such - this.props.changeGreen()
Related
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 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,
),
})
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)
In the example below, I am using mapDispatchToProps to bind the onSubmit event to the save() Action Creator. This works - the save() Action Creator logs the message to console.
The issue is that it does not subsequently dispatch the 'TEST_SAVE' action - i.e. the reducer never receives an action of type 'TEST_SAVE'.
I have a vanilla redux form working as below, but I am new to redux-form and I wonder what I might be doing wrong?
const reducers = {
// ... your other reducers here ...
form: formReducer.plugin({
contact: (state, action) => {
switch(action.type) {
case 'TEST_SAVE':
return state;
default:
return state;
}
}
})
};
const reducer = combineReducers(reducers);
const store = createStore(reducer);
function mapDispatchToProps(dispatch) {
return {
onSubmit: (val) => dispatch(actionCreators.save(val)),
}
}
var actionCreators = {
save: (value) => {
console.log('SAVE action done!');
return {type: 'TEST_SAVE', value};
},
};
const MyContactForm = connect(
mapDispatchToProps
)(ContactForm)
<Provider store={store}>
<MyContactForm />
</Provider>
An excerpt of the ContactForm class:
class ContactForm extends Component {
render() {
const { error, handleSubmit, pristine, reset, submitting } = this.props;
return (
<form
onSubmit={handleSubmit}
>
....
)
}
}
ContactForm = reduxForm({
form: 'contact'
})(ContactForm);
const MyContactForm = connect(
mapDispatchToProps
)(ContactForm)
The first parameter to connect() is mapStateToProps, not mapDispatchToProps. Should be:
const MyContactForm = connect(
undefined, // <------------- mapStateToProps
mapDispatchToProps
)(ContactForm)
Never use break inside a reducer. Always return the state object.