React DOM not updated when prop from redux store changes - reactjs

This is driving me crazy for hours now... I have a module that displays a list that is fetched from a server and loaded into the redux store on button press. That works properly. I mention this as this is the reason why I don't understand the following behavior.
This object array from the store is mapped into my component with
const mapStateToProps = (state) => {
return {
extracted_templates: state.extracted_templates
}
}
And used in the render() as follows... I removed some other DOM parts to keep it simple
render(){
return(
<div className="main-container">
{Object.values(this.props.extracted_templates).length > 0 ?
<ExtractedTemplatesList templates={Object.entries(this.props.extracted_templates)} clickHandler={this.clickHandler} /> : '' }
</div>
);
}
The clickHandler modifies the store using the same action as the fetch function uses.
clickHandler(action, id, parent){
console.log(action+" "+parent)
switch(action){
case 'dismiss':
let new_template_list = this.props.extracted_templates
delete new_template_list[id]
// console.log(new_template_list)
this.props.dispatch(setExtractedTemplates(new_template_list))
break;
default:
break;
}
}
Everything is called correctly, the store updates correctly (as I can see in my web-dev console) but this time the DOM doesn't get updated.
For completeness, here's the action and the reducer implementation
action:
export const setExtractedTemplates = (templates) => ({
type: actions.SET_EXTRACTED_TEMPLATES,
payload: templates
});
reducer:
case actions.SET_EXTRACTED_TEMPLATES:
console.log({action})
return {
...state,
extracted_templates: action.payload
}

You're mutating the existing data, and you're putting the exact same object back into the store:
let new_template_list = this.props.extracted_templates
delete new_template_list[id]
this.props.dispatch(setExtractedTemplates(new_template_list))
Both of those are bugs. You should never mutate data from the store, and the result of an action should be new data in the store.
This is one of the reasons why we recommend putting as much logic as possible into reducers. Also, you should be using our official Redux Toolkit package, which would both catch this accidental mutation here, and simplify the update logic in a reducer.

Try this:
clickHandler(action, id, parent){
console.log(action+" "+parent)
switch(action){
case 'dismiss':
let new_template_list = {...this.props.extracted_templates} //make a new copy
delete new_template_list[id]
// console.log(new_template_list)
this.props.dispatch(setExtractedTemplates(new_template_list))
break;
default:
break;
}
}
You modified the same object saved in the redux store. This is potentially dangerous because you changed the state without using a reducer. When React did the shallow comparison, it didn't see difference so UI was not updated. You can make a copy before save it to store.
Further more you can modify your reducer in this way:
case actions.SET_EXTRACTED_TEMPLATES:
console.log({action})
return {
...state,
extracted_templates: [...action.payload] //make a new copy
}

Related

Implementing reselect in redux prevents new changes to appear instantly

In my React project, I have implemented memoization using reselect library.
The state basically has a list of objects which I render as cards.
Before implementing reselect, whenever I added a new element, the change instantly showed up and a new card got added at the end. However, now when I add a new element it does not instantly shows up, but rather shows up when the page is reloaded.
Why does this happen? And is there a way to fix this without removing the use of reselect library
EDIT : The issue has been solved, and as pointed out in the answers it was because I was simply mutating the state
The earlier code was as follows
case IssueActionTypes.ADD_ISSUE:
state.issueList.push(action.payload)
return {
...state
}
which I replaced with
case IssueActionTypes.ADD_ISSUE:
return {
...state,
issueList : [...state.issueList, action.payload]
}
which fixed the issue
Most likely you are returning mutated state in your reducers instead of returning a new array.
Docs:
createSelector uses an identity check (===) to detect that an input
has changed, so mutating an existing object will not trigger the
selector to recompute because mutating an object does not change its
identity. Note that if you are using Redux, mutating the state object
is almost certainly a mistake.
Example of returning mutated state (from docs):
export default function todos(state = initialState, action) {
switch (action.type) {
case COMPLETE_ALL:
const areAllMarked = state.every(todo => todo.completed)
// BAD: mutating an existing object
return state.map(todo => {
todo.completed = !areAllMarked
return todo
})
default:
return state
}
}

How can I update my redux state correctly?

I am facing a problem, I am new to Redux, and I am just playing around with it, So I have a problem, I created a data json file, I get the data from it in my reducer, and everything works fine, here is my reducer :
import update from "immutability-helper";
import data from "../../../../data";
export default function notificationsReducer(state, action) {
switch (action.type) {
case "NOTIFICATIONS_EDIT_TO_FOLLOW":
return update(state, {
[action.id]: {
follwing: { $set: false }
}
});
break;
case "NOTIFICATIONS_EDIT_TO_UNFOLLOW":
return [];
break;
default:
return data.notifications;
}
}
As you can see, in the default part, I return data.notififications, this causes a probleme of course, because the data in the json file does not change, the state does.
When I work on my component and click 'follow' and 'unfollow' and stuffs everything looks fine, but When I click somewhere else outside the component, others actions got dispatched, ( others for different purposes ) and the default part get executed again, so when I open my notifications again all changes I made are gone.
That's my problem, if any explanation from me is needed just ask and I will edit my question.
Any help would be much appreciated.
Recapping from the comments:
In your default case (for all reducers), you need to return the current state of your reducer so that unrelated actions don't interfere with their values:
default:
return state;
You also (if needed) have to set up the initial value of your reducer, in this case the notifications from the data JSON file:
export default function notificationsReducer(state = data, action) {...}

immutable react reducer state is not updating

I am trying to create a simple website using react-redux and the immutable-assign library (instead of immutable) to handle my state. (documentation for immutable-assign: https://github.com/engineforce/ImmutableAssign)
I've made solutions with both the 'immutable' and 'immutable-assign' libraries, but neither work (code for immutable solution is commented out in the reducer below. No matter which changes I make, the state never changes, and the values are never assigned to menuItems
The setMenu(newMenu) function is currently called with dummydata in the form of a list of arrays in the following format:
menuItems: {
id: "113",
foodItem: "tesatewr",
description: "gfdgsdfsdf",
price: 999
}
The reducer:
import { iassign } from 'immutable-assign'
export function setMenu(newMenu) {return {type: 'SET_MENU_ITEMS', newMenu}}
const initialState = {
date: 'test',
menuId: 'test',
menuItems: []
}
function menuViewReducer(state = initialState, action){
switch(action.type){
case 'SET_MENU_ITEMS':
var itemList = iassign(
state,
function (n) { n.push('testtest'); return n}
)
return state.set(['menuItems'], itemList)
default:
return state
}
}
/* CODE FOR IMMUTABLE
function menuViewReducer(state = fromJS(initialState), action){
switch(action.type){
case 'SET_MENU_ITEMS':
return state.updateIn(['menuItems'], (menuItems) => menuItems.push(fromJS(action.newMenu.menuItems)))
default:
return state
}
} */
export const menuSelector = {
date: state => state.menuViewList.date,
menuId: state => state.menuViewList.menuId,
menuItems: state => state.menuViewList.menuItems
}
export default menuViewReducer
Render function:
render(){
return (
<div>
Test data here: {this.props.menuItems}
<ul className="menuViewList">{ this.mapMenuItemsToListElements() }</ul>
<button
onClick={() => this.mapMenuItemsToListElements()}> get data
</button>
</div>
)
}
It's really hard to figure out what's not working from this code. The best I can do is give you some debugging tips:
First off, are you getting any errors? If yes, that seems like a good place to start.
Otherwise, try to narrow down where the problem is occurring.
Are you sure your reducer is actually getting called?
I would try putting a console.log right after your case 'SET_MENU_ITEMS': so you know when your code is being run.
If it's not:
The problem could be a number of things:
Your reducer isn't connected to your store properly
You're not properly dispatching actions to your store
The actions you're dispatching don't have their type property properly set.
If it is:
The problem could be a number of different things. Some that I can think of:
Your state isn't being updated (properly). Try logging the state at the start of your reducer and your new state right before you return it. Or consider using redux-devtools to inspect your state.
Your view isn't getting updated. Maybe your component isn't connected to your store properly.
I found the error and as Simon pointed out, its hard to find from my submitted code.
I was calling setMenu(newMenu) in a generator function, so I should have called it like this:
yield put(setMenu(newMenu))
instead of
setMenu(newMenu)

Reusable component using React Redux mapStateToProps

A React component OilBarrel connected my redux store to create a container OilBarrelContainer:
// ---- component
class OilBarrel extends Component {
render() {
let data = this.props.data;
...
}
}
// ---- container
function mapStateToProps(state) {
let data = state.oilbarrel.data;
...
}
const OilBarrelContainer = connect(mapStateToProps)(OilBarrel)
// ---- reducer
const oilbarrel = (state = {}, action) => {
let data = state.data;
}
const storeFactory = (server = false, initialState = {}) => {
return applyMiddleware(...middleware(server))(createStore)(
combineReducers({oilbarrel, otherReducer1, otherReducer2}),
initialState
)
}
I find it strange that mapStateToProps() receives the top level state object (the entire state of the application), requiring me to traverse state.oilbarrel.data, when the reducer (conveniently) only receives the branch of the state that belongs to this component.
This limits the ability to reuse this container without knowing where it fits into the state hierarchy. Am I doing something wrong that my mapStateToProps() is receiving the full state?
That is the mapStateToProps behavior. You have to think redux state as a single source of truth (by the way, that is what it really is) independently of the components you have in project. There is no way out, you have to know the exactly hierarchy of you especific data in the state to pass it to your container component.
No this is intentional, because you may want to use other parts of the state inside your component. One option is to keep the selector (mapStateToProps) in a separate file from your component, which will help you reuse the selector, if you app is very large and complex you can also checkout libraries such as reselect which helps you make your selectors more efficient.
Dan Abramov offers a solution for this in his advanced redux course under Colocating Selectors with Reducers.
The idea is that for every reducer, there is a selector, and the selector is only aware of it's reducer structure. The selectors for higher level reducers, wrap the lower level reducer, with their part of the state, and so on.
The example was taken from the course's github:
In the todos reducer file:
export const getVisibleTodos = (state, filter) => {
switch (filter) {
case 'all':
return state;
case 'completed':
return state.filter(t => t.completed);
case 'active':
return state.filter(t => !t.completed);
default:
throw new Error(`Unknown filter: ${filter}.`);
}
};
In the main reducer file:
export const getVisibleTodos = (state, filter) =>
fromTodos.getVisibleTodos(state.todos, filter);
Now you can get every part of your state without knowing the structure. However, it adds a lot of boilerplate.

my reducer is executed but state does not update in react

I am using recat redux for my project and in one component I need to update my state but since I am dealing with asynchronous call I need to do the action call in my componentDidUpdate as follows:
componentDidUpdate() {
this.props.updateHamburgerMenu(this.props.Channel.channelIdArr);
}
and here is my action:
export function updateHamburgerMenu(channelIdArr) {
return dispatch => {
dispatch(
{
type: "UPDATE_HAMBURGER_MENU",
payload: {
"channelIdArr":channelIdArr
}
}
);
};
}
and in my reducer I have :
switch (action.type) {
case "UPDATE_HAMBURGER_MENU":
var channelList=state.allChannelList.slice();
channelList.unshift({
"id": channelIdArr[0],
"channelName": "sssssssss",
"status": "Inactive"
});
alert("reducer called");
state.allChannelList=channelList;
break;}
return state;
Now when I run it I can see that the alert is working but state does not update at all.
Also I tried another way as follow:
state={"channelsArr":state.channelsArr,"AllChannels":state.AllChannels,"channelIdArr":state.channelIdArr,"channelLabelForScrolls":[], "latestAction":action.type, "allChannelList":channelList};
break;
This way, it seems that state keep updating and it goes in infinite loop.
It is really confusing, can anyone help? what am I missing?
Update:
When I separate the allChannelList in another reducer it works. So it seems that updating allChannelList in a specific case of componentdidupdate goes to infinite loop and state keep updating itself. BUt I have no idea why it is happenning
in your reducer case statements, you should be returning a new object which represents the state after the current action - you appear to be trying to assign directly to the allChannelList property on the passed in state object.
i.e.
return {
...state,
allChannelList: channelList
};

Resources