I have two container components in my app. I just started react, redux and thought that a container component gets state object from its respective reducer so that it can be mapped to the component's props.
I console logged the state sent by the reducer and saw that the state object contains the data sent by the other reducer in my app as well. I thought it's supposed to get data only from its corresponding reducer because the reducer is what sends data to a container component.
I don't know if I'm doing something wrong or if that's how it is.
The reducer:
export default function(state=null, action){
console.log(action);
switch(action.type){
case 'BOOK_SELECTED':
return action.payload
}
return state;
}
The container component:
function mapStateToProps(state){
console.log("state from reducer",state); //contains data from the other reducer as well
return {
bookDetail : state.bookDetail
}
}
export default connect(mapStateToProps)(BookDetail);
Containers and reducers are not "connected". That's why mapStateToProps exists.
Redux doesn't know what parts of the state a container needs, so, in mapStateToProps, all the state is provided and each one takes whatever it needs. In your case, your container needs state.bookDetail.
Hope it helps.
If you check react-redux documentation, you'll see that mapStateToProps receives the full store data as first argument. It's your job to slice that to the props needed by the connected component, as you are already doing. Your component will only receive bookDetail as a prop from the state.
So your code is fine, although I would rewrite that switch statement in the reducer with a default clause.
Related
I initially had my reducer as such, and it does not work (my updated redux state does not lead my component to update, my component's prop 'RecipeStore' is mapped onto the redux state's 'myRecipes' property)
const initialState={
myRecipes: []
}
export default function(state=initialState, action){
switch(action.type){
case SUBMIT_RECIPE:
const newState={...state}
newState.myRecipes.push(action.payload)
return newState
default:
return state;}}
I figured that my component did not rerender with redux store update because I mutated state, and you should never mutate state. So I rewrote the reducer as such:
const initialState={
myRecipes: []
}
export default function(state=initialState, action){
switch(action.type){
case SUBMIT_RECIPE:
console.log("reducer invoked")
const copyState={...state, myRecipes:state.myRecipes.concat(action.payload)}
return copyState
default:
return state;
}
}
And this time it worked - my component updates every time the redux store changes. However - my original reducer also created a copy of the original state using the spread operator, and only mutated the copy, why doesn't it work?
This is one of these situations where I'm glad that I solved the problem but am peeved that I don't know why.
Can someone lend me their thought?
In Redux, Suppose I have two different un-connected states in redux
const rootReducer = combineReducers({
navBar: navBarReducer,
exchange: exchangeReducer
})
If I dispatch actions which changes state for navBar, would mapStateToProps trigger render for exchange: exchangeReducer as well?
First of all, mapStateToProps is related to the render method and not reducers.
Secondly, change in state from one reducer would update the entire component subscribed to it(Unless you have some conditions to pause rendering).
So to answer your question change in one reducer would only reflect a change in components subscribed to it.
I am trying to pass props to child component but the child is always receiving an empty.
Here's the code to make it clearer.
When I pass to child component a state, it works
Here's the working code:
render() {
return (
<div>
<PostList list={ this.state.posts }></PostList>
</div>
);
}
But in my case, I want pass the props from the redux state
Not working code:
render() {
return (
<div>
<PostList list={ this.props.posts }></PostList>
</div>
);
}
}
function mapStateToProps(state) {
return {
posts: state.posts.all,
postDetail: state.posts.post
}
}
Any ideas why the PostList is getting an empty object in the second case even though this.props.posts is not actually empty in the calling component?
Thanks
Install redux-devtools to see what your redux state actually looks like. For example, if you have used combineReducers(), then .posts may be prefixed with the name of a reducer.
Once you have verified the actual redux state, then double-check mapStateToProps to ensure you're referencing the correct state slice. In your question, I wonder if you may need to adjust it, but I cannot be sure without first knowing what your redux state is.
function mapStateToProps(state) {
return {
posts: state.posts
}
}
Please confirm what you are exporting. I assume your render() method is contained within a class? If so, ensure you export the result of connect(). Something like this:
export default connect(mapStateToProps, mapDispatchToProps)(MyReactClass)
Shouldn't you just attach to store in your child component? You can pass information where to attach if it's dynamic. Though passing store value by props sounds like a bad idea.
as for your example - this.props.posts props are intial values, re-render is not caused by props change inside your scope. Clearer explaination:
If parent changes props of child component - that cause re-rerender.
If parent changes it's own props then no re-rerender triggered. (this.props shouldn't be mutated)
If you want to cause an action use state - pass props to state. Then any manipulation on state will cause re-render of child.
<PostList list={ this.state.posts }></PostList>
Attaching to redux-state is triggered after your props are created therefore it's empty when child is called and (as said above) it won't trigger re-render.
edited:
Well, my answer was a bit chaotic. Let me try again.
Solution:
attach to store or wherever you get your data and pass this data to
this.state.posts = data. In constructor of your component set default state, like this.state = { posts: [] } (or whatever structure is required). In render() change your call for child into <PostList list={ this.state.posts }></PostList> and there you have it.
Notice:
Anyways since you're using redux I believe it would be better if you'd just attach to redux store inside your child component.
Redux is successfully storing and updating state. The reducers are seemingly working correctly. I'm able to use this.props.dispatch. However, when it actually comes to detailing that information (i.e. this.props.array I always seem to get undefined.
Reducer:
export default function array(state = {}, action) {
switch (action.type) {
case "UPDATE_ARRAY":
state.array = action.array
return state;
default:
return state;
}
}
State-aware component:
constructor(props) {
super(props);
this.state = {
array: array
}
}
--
self.props.dispatch({
type: 'UPDATE_ARRAY',
array: array
})
--
const mapStateToProps = (state) => {
return {
messages: state.messages,
array: state.array
};
};
export default connect(mapStateToProps)(Component);
This only seems to be able to save state btw when I define an empty array. This doesn't seem right, I thought the intention of Redux was a self-contained store? Updating a variable seems to defeat the purpose a bit.
Would appreciate any help.
export default function array(state = {}, action) {
switch (action.type) {
case "UPDATE_ARRAY":state={
...state,
array:action.array
}
return state;
default:
return state;
}
}
you should always update your state immutably,instead of mutating the current application state ,you should create another object and return that.State should be immutable ,only way to change the state is to create a new one.This helps to improve the performance of the application.
I am not sure if you application has more than one reducer or not, if it has, than you must be using combine reducer method .So to access state.array in mapsStateToProps is like this
const mapStateToProps = (state) => {
return {
messages: state.{reducer_name}.message,
array: state.{reducer_name}.array
};
};
in place of 'reducer_name' you have to specify the reducers_name which you have define in combine reducer
And last mapStateToProps return array ,in props not in component state.
which you can access in this way {this.props.array},you cant set component state in componentDidMount and in componentWillRecieveProps (in case of aysnc action).
Your component will receive array as a field in its props field. Your code assumes it's in the state field. So instead of:
this.state = {
array: array
}
you would just access this.props.array wherever in your code you need to use the array. You don't need to put it in the local state at all. Usually, you would use it in the render function, like in this example:
render()
{
return <div>The array contains {this.props.array.length} items.</div>
}
I wonder if you're confusing local state with the Redux store's state? Local state is what you get/set when you access this.state in your component code. Every component can have its own state object that it can read from and write to.
The Redux store's state is what's passed in to mapStateToProps. It's usually the entire state object of all the combined reducers in your top-level reducer (though if you only have one reducer function and are not using combineReducers, then the store state is identical to that single reducer's state).
I suggest choosing more descriptive variable names, so that your code will be more readable. It's hard to understand what your intentions are for your code with such generic names. For example, you could name your reducer something that indicates what it's for, like bookListReducer, and name the array you want to store and retrieve for what will go inside it, like books. Naming both your reducer and all your variables array makes it harder to read your code. This will help anyone who reads your code in the future - including, most importantly, you!, as well as future Stack Overflow readers of your future questions (and perhaps this one if you edit it).
I am not sure the following is your issues, but hope these will help:
export default function array(state = {}, {type, array}) {
switch (type) {
case "UPDATE_ARRAY":
return {...state, array};
default:
return state;
}
}
Your reducer should be pure, which you had is mutating the state.
constructor(props) {
super(props);
this.state = {
array: array // what is array?
}
}
Above constructor is not right. You should be able to access the array from this.props.array as your mapStateToProps
Do a console.log(this.props) in your render function or ComponentWillReceiveProps, see if you can something :)
I have redux store that looks something like this:
{
user: {},
alerts: [],
reports: [],
sourses: []
}
For each one of this parts of state i have a bunch of React Components wrapped in a container wich connected via react-redux. And has mapStateToProps like this
(state) => {alerts: state.alerts}
(state, ownProps) => {alert: _.filter(state, {id: ownProps.curId})}
Problem that when i for example launch some action for Alerts like CREATE_ALERT or EDIT_ALERT and redux state updated, ALL REACT COMPONENTS WILL RESPOND TO THIS CHANGE even ones that works with different parts like sources or reports.
My question: how to "bind" certain components to certain parts of a tree. So each container component WILL UPDATE ONLY WHEN APROPRIATE PART OF REDUX STATE UPDATED and ignore other changes.
Expected behavior
Dispatch CREATE_ALERT -> Alert reducer -> Redux store update -> ONLY Alert container component re-rendering.
When you are changing state in redux the whole state becomes just a new object.
Then your component is given by this new object (new reference) and re-renderes itself.
To fix this behaviour you need to add some logic to compare if your component got props with different value (not reference).
The easiest and fastest way is to use React.PureComponent. You can also override shouldComponentUpdate function and handle changes by yourself. But note that PureComponent works only with primitives (it does a shallow compare).
Check also Immutable.js which helps you with intelligent way of changing references of props.
if you use connect method, then pass only selected redux state to the component, this will prevent rendering of other components
example:
User Component:
const mapStateToProps = state =>({
users: state.users
});
export default connect(mapStateToProps)(User)
Alert Component:
const mapStateToProps = state =>({
alerts: state.alerts
});
export default connect(mapStateToProps)(Alert)
Check this out: Avoid Reconciliation
There explains what Neciu says.
Container components created with connect will always receive notifications of all updates to the store.
The responsibility for consuming these updates falls on the receiving connect component. It should contain the logic to extract the data relevant to it.