Redux, Do I have to import store in all my containers if I want to have access to the data?
Solution 1:
In general you want to only make top-level container components ones that have access to the store - they will pass down any necessary data or action dispatches as props to their children components. This is the difference between a "smart" and a "dumb" component - "smart" components know about the Redux store/state, while "dumb" components just get props passed to them and have no idea about the bigger application state.
However, even just passing the store to container components can become tedious. That's why React-Redux provides one component out of the box that wraps your entire application. Check it out in the docs. This is the Provider
component and when you wrap your whole app with it, you only pass the store to a component once:
import createStore from '../store';
const store = createStore()
class App extends Component {
render() {
return (
<Provider store={store}>
<MainAppContainer />
</Provider>
)
}
}
As you can see here, I have a separate configuration file just for my store as there is a lot of modification you can do and for any remotely complex app, you'll find yourself doing the same for things like using compose to apply middleware.
Then any of your remaining "smart" components (generally wrappers) need to listen to the store. This is accomplished using the connect method. This allows you to map pieces of the state to your component properties as well as dispatch actions as properties.
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actionCreators from './actionCreators';
const mapStateToProps = function(state){
return {
something: state.something,
}
}
const mapDispatchToProps = function (dispatch) {
return bindActionCreators({
getSomething: actionCreators.getSomething,
}, dispatch)
}
class MainAppContainer extends Component {
componentDidMount() {
//now has access to data like this.props.something, which is from store
//now has access to dispatch actions like this.props.getSomething
}
render() {
//will pass down store data and dispatch actions to child components
return (
<div>
<ChildComponent1 something={this.props.something} />
<ChildComponent2 getSomething={this.props.getSomething} />
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MainAppContainer)
Because you are always passing down dispatch actions and data to your children component as properties, you just reference those on that component with this.props
.
Building off the example above, you'll see that because I passed this.props.something
to ChildComponent1
, it has access to the something
data from the store but it does not have access to the getSomething
dispatch action. Likewise, ChildComponent2
only has access to the getSomething
dispatch action but not the something
data. This means that you only expose components to exactly what they need from the store.
For example, because ChildComponent2
was passed down the dispatch action as getSomething
, in my onClick
I can call this.props.getSomething
and it will call the dispatch action without needing any access to the store. In the same way it can continue to pass down getSomething
to another child component and that component could call it and/or pass it down and the cycle could continue indefinitely.
class ChildComponent2 extends Component {
render() {
return (
<div>
<div onClick={this.props.getSomething}>Click me</div>
<NestedComponent getSomething={this.props.getSomething} />
</div>
)
}
}
Edit from the comments
While this doesn't pertain directly to the question, in the comments you seemed a little confused about actions. I did not actually define the action getSomething
here. Instead it is usual in Redux apps to put all of your action definitions in a separate file called actionCreators.js
. This contains functions that are named the same as your actions and return an object with a type
property and any other methods/data that action requires. For instance, here's a very simple example actionCreators.js
file:
export function getSomething() {
return {
type: 'GET_SOMETHING',
payload: {
something: 'Here is some data'
}
}
}
This action type is what your reducer would listen for to know which action was being fired.
Solution 2:
If you use the react-redux
package, you'll end up wrapping your components in a Provider
with a store
prop. This sets up your single store in a React context, which is then accessed from the connect
method in child components. The connect
method takes two functions (mapStateToProps and mapDispatchToProps), which are your hooks for getting state from the store and dispatching messages.
Take a look here for more information