I apologize if this is something that has already been cleared up, but a keyword search in the issues didn't turn up anything that helped.
If I want to call store.dispatch(someAction)
from a component file, what is the correct way to access the store
instance?
Right now I have the following, which works, but I'm not sure that it's the right approach:
index.jsx:
...
const store = applyMiddleware(
loggingMiddleware, // log every action
thunk // making API requests from actions
)(createStore)(reducer);
export {
store
};
...
Controls.jsx
import {store} from './index.jsx';
import * as actions from './actions';
...
let clickHandler = function(node, data) {
store.dispatch(actions.nodeClicked(data))
}
export const Controls = React.createClass({
render: function() {
// React component gets rendered, and it passes the clickHandler to a
// function that attaches it to a D3 visualization
}
});
Is there anything wrong with exporting/importing the store like this? I think I could just create the click handlers as part of the React component and call this.context.store
, but in reality there are about a dozen different event handlers that get passed to the D3 visualization, so that's not really practical in this situation.
Check out https://github.com/rackt/react-redux for a helper method to bind dispatch to your components
Specifically, from https://redux.js.org/docs/basics/UsageWithReact.html
Any component wrapped with connect() call will receive a dispatch function as a prop, and any state it needs from the global state.
Is there anything wrong with exporting/importing the store like this?
This won't work if you render your app on server because you want to have different store
instance per request on server. That's why we never recommend this approach in the docs.
Why not do this instead?
import { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from './actions';
let createHandlers = function(dispatch) {
let onClick = function(node, data) {
dispatch(actions.nodeClicked(data))
};
return {
onClick,
// other handlers
};
}
class Controls extends Component {
constructor(props) {
super(props);
this.handlers = createHandlers(this.props.dispatch);
}
render() {
// pass this.handlers anywhere you'd like
}
}
export default connect()(Controls);
@gaearon Looks great, thank you!
@gaearon Actually, after trying it out, I'm getting undefined
for this.props.dispatch
.
The relevant parts of my code:
import React from 'react';
import {Component} from 'react';
import {connect} from 'react-redux';
import * as vizActions from './vizActions';
import viz from './lib/viz';
let createHandlers = function(dispatch) {
return {
click: function(DOMNode, data) {
// Getting undefined here
dispatch(vizActions.setSelectedNode(data));
}
};
}
class Viz extends Component {
constructor(props) {
super(props);
console.log(this.props.dispatch); // <- Getting undefined here
// Passing the redux dispatch to be used in handlers function
this.handlers = createHandlers(this.props.dispatch);
}
componentDidMount() {
// Create Viz
}
componentDidUpdate() {
// Update Viz Data, passing in this.handlers
}
render() {
return <div id="viz"></div>;
}
};
function mapStateToProps(state) {
return {
// Pass the network used for the viz as a prop
network: state.get('visualization').get('network')
};
}
export const VizContainer = connect(mapStateToProps, vizActions)(Viz);
However, after reading another issue (https://github.com/rackt/redux/issues/239) I realized that I am actually binding my actions to this.props
via connect()
, so I can just pass the necessary action when I call createHandlers()
. Maybe this is not the best approach but it works now:
...
let createHandlers = function(action) {
return {
click: function(DOMNode, data) {
// This works now
action(data);
}
};
}
class Viz extends Component {
constructor(props) {
super(props);
// Passing the redux dispatch to be used in handlers function
this.handlers = createHandlers(this.props.setSelectedNode); // passing action creator function
}
...
Actually, after trying it out, I'm getting undefined for this.props.dispatch.
Because you are not running the code I posted. In the code I posted connect
did not receive second paramater. You pass vizActions
there. That's why you don't get dispatch
. If you remove vizActions
from connect
call you will get it.
Both approaches are equivalent though so go with what reads best to you.
:+1: Ok, that makes sense now. Thanks for the help!
Kinda late to the party, but replying just in case it helps someone later (including myself).
If a custom mapDispatchToProps
is needed (the second param passed to connect), explicitly add dispatch
as one of the keys.
const mapDispatchToProps = (dispatch) => {
return {
dispatch,
// ... other custom mapped dispatch functions ...
myFunction: (myParam) => {
dispatch(State.Actions...)
},
}
}
export default connect( mapStateToProps, mapDispatchToProps )(Viz);
Hi. I also have this problem.
@gaearon , i'm grateful for your guidance. I've used your solutions, but I can not understand and use them.
I do not know how to use this.handlers
When the user clicks on a card, I send that card id to the parent's components and run the action.
import React , { Component } from 'react';
import { Col } from "react-bootstrap";
import { connect } from 'react-redux';
import Home from "../components/Home";
import { fetchHome, asideArticle } from "../actions/index";
let createHandlers = function(dispatch) {
let onClick = function(node, articleId) {
dispatch(asideArticle(articleId))
};
return {
onClick
};
}
class HomeContainer extends Component {
constructor(props) {
super(props);
this.handlers = createHandlers(this.props.dispatch);
console.log('constructor => this.props.dispatch => Result => dispatch is undefined);
}
componentDidMount() {
this.props.fetchHome();
}
render() {
const { homeData, article } = this.props;
//
return (
<Col>
<Home homeData={homeData} asideArticle={(articleId) => asideArticle(articleId) } />
</Col>
);
}
}
const mapStateToProps = state => ({
homeData : state.home,
article: state
});
function loadData(store){
return store.dispatch(fetchHome());
}
export default {
loadData,
component: connect(mapStateToProps, { fetchHome, asideArticle })(HomeContainer)
}
How can I use this.handlers
and pass the articleId
after passing the click event to pass it ?
And what does the node
parameter mean in the function(node, articleId)
and what does it do?
@EhsanFahami :
This is a bug tracker, not a support system. For usage questions, please use Stack Overflow or Reactiflux where there are a lot more people ready to help you out - you'll probably get a better answer faster. Thanks!
Most helpful comment
This won't work if you render your app on server because you want to have different
store
instance per request on server. That's why we never recommend this approach in the docs.Why not do this instead?