Understanding mapStateToProps - javascript

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.

Related

What should be the name of state parameter while fetching data from redux store?

I am working on a react application with redux. I am confused on how I should access the data in my redux state, like store.Like?
In my component, I am using mapStateToProps. What should be after state. in order to access the data?
const mapStateToProps=state=>({
data:state.cardItems
})
What should be after state._____ (in place of cardItems)in order to assign state data to data.
Here is how I combine the reducers:
export default combineReducers({
cardItems,
})
You can check my app here.
Thanks
Assign your initialState to state in reducer.

Child component not re-rendering from state change from one parent, but re-renders from another parent?

I have a child component PostItem that takes a post object from redux state as a prop:
const PostItem = ({
post: { _id, text, name, avatar, user, likes}, //from redux state
//...
}) => {
//...
const handleLike = e => {
likePost(_id); //ACTION
};
return (
<Fragment>
<div className='post bg-white vert-m-1 p-1'>
<div>
<p className='vert-m-1'>{text}</p>
<button className='btn' onClick={e => handleLike(e)}>
<i className='fas fa-thumbs-up'></i> <span>{likes.length}</span>
</button>
</div>
</Fragment>
);
}
I have an action likePost that changes state in a reducer:
case LIKE_A_POST:
case UNLIKE_A_POST:
return {
...state,
posts: state.posts.map(post =>
post._id === payload.id
? {
...post,
likes: payload.likes
}
: post
),
loading: false
};
To my knowledge, React component re-renders upon state or props change.
I render the child component, PostItem, from 2 parent components: Posts and Comment.
If PostItem is rendered from Posts, triggering the action likePost will automatically re-render the component. This makes sense because my redux state changes which will cause a re-render. In addition, since the redux state is passed as a prop to PostItem, changing the redux state will change the props which also triggers a re-render.
However, when PostItem is rendered from Comment, triggering likePost does not cause PostItem to re-render even though the likePost action still changes my redux state. I can see in the redux Developer tools that the state is in fact changed, but a re-render is not triggered. In this case, the redux state is also being passed as a prop to PostItem, but a change in props is not triggering a re-render.
Why is PostItem re-rendering in the Posts parent component, but not Comment parent component? Both Posts and Comment pass a post object (containing my redux state) as a prop to PostItem.
Following advice online, my reducer uses the spread operator to avoid using the same object reference. Both Posts and Comment use the same action to change my redux state, meaning state is changing in both cases. Both Posts and Comment pass the redux state as a prop to PostItem, meaning props is being changed in both cases.
For full reproducible code, I'm not sure how to proceed since it
involves accessing a database, requiring my database URI as well as a
JSON Secret Web Token. Advise on how to provide a Minimal,
Reproducible Example please.
A link to my repo: https://github.com/boxcarcoder/ExplorersConnect
Please look in the reprex branch for a minimal version of the
following files:
The components PostItem, Posts, and Comment: explorersConnect/client/src/components/posts
The actions: explorersConnect/client/src/actions
The reducers: explorersConnect/client/src/reducers
The key is to realize that although likePost changes the likes of a post, this post is not the post redux state, but a post within the posts array redux state. That means triggering likePost is triggering a change to posts, which is a prop to Posts, but not Comment. The wrong assumption was that changing Posts will be a state change that triggers Comment to re-render, but Comment's props does not change so there is no re-render.
Since Comment's props are post, the likePost action and reducer should change post in the redux state, so that a change in props and state will trigger a re-render of Comment.
There was confusion between changing state:
Changing state.posts is not the same as changing state.post, even though state.posts is, in theory, just an array of all state.posts. The key is understanding that a change in a component's Props is necessary, not just a state change. The state change has to directly affect a component's props.

React Redux: How to share data between reducers?

I have a basic todo list. The todo input field for the todo list has an onChange event that triggers an action and sends event.target.value to a reducer and stores each character the users types to a property of the store object.
When the user submits the form I want to get the data that was previously stored via the onChange event, then I want to place it on a new property on the store object.
How do I get data that was previously entered from the store and pull it into a different reducer?
In all the examples I've seen, reducers start with an "initial state". I don't want that, I want the previous state that the user entered.
Below is a CodeSandbox version of the code (for some reason the orange-beige tab to the right needs to be switch to the left to blue for it to render the form. If you don't do that it won't work).
https://codesandbox.io/s/pwqlmp357j
Ask yourself whether you've structured your reducers correctly. If a
and b are not independent of one another, why are they separate
reducers?
Well if we talk about correctly structuring the reducer, cachedInput and todoList should live in single reducer. There is no need to create another reducer. Also I guess you need to grab the input value all at once(when user clicks on submit) and send it to store.
If still you want to create seperate reducers then you can follow the approach given below :
Answer to How to access or share data already in store, inside the reducers?
Don't use combineReducers.
Example
replace this code
export const a = combineReducers({
cachInput,
newTodo
});
with
export default (state = {}, action) => {
return {
cachInput: cachInput(state.app, action, state),
newTodo: newTodo(state.posts, action, state),
};
};
reducer would be like
const reducer = (state = initialState, action, root) => {....}
and you can access the previous state from root
onChange event that triggers an action and sends event.target.value to a reducer and stores each character the users types to a property of the store object
Practically, this is not right way to use to redux->action->reducers->store considering you are submitting the form to send the same data to reducers.
This will caused un-necessary rendering of component connected with redux#connect if you have not handled shouldComponentUpdate /componetWillRecieve/getDerivedStateFromProps nicely
Instead store the each character/string that you typed, inside component state and once the user submit it, dispatch the action to pass the string/characters to reducer's.
You are using two different state(or say initialState) for each reducer. Hence update in one object is not reflecting in the other.
You need to use the shared initialState for both of the reducer and your problem is solved.
You can keep initialState in different file and you can import in both the reducers.
Consider below code sample:
1.InitialState.js
export default initialState = {
todoList: [
{ text: "fake todo" },
{ text: "fake todo" },
{ text: "fake todo" }
],
cachedInput: ""
};
2.Now in CachDataOfTodoInput.js
import initialState from "InitialState";
export function cachInput(state = initialState, action)
3.Same in SubmitDataToTodo.js
import initialState from "InitialState";
export function submitNewTodo(state = initialState, action)
Hence intialState is shared between your reducers and you will be able to access data in each reducer.
If you want to load your application with initial state that contains previously cached data and if you are using client side rendering, you may insert cached data in redux store into local storage and use that as initial state.
If you just want data to be imported to the other reducer after form submission, you may use redux-thunk. It lets you load current state in your submit action and pass the catches values into the other reducer and store them.
if you are using ssr and you want data be loaded on server there is no way unless saving data on server ( in a file or database), read the data when request is received on server and update your redux store. It will load data all over your app.

Why does my Container component get state object from other reducers?

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.

Bind react component to part of redux state

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.

Categories

Resources