my reducer is executed but state does not update in react - reactjs

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
};

Related

React DOM not updated when prop from redux store changes

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
}

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) {...}

Redux - get initial state from the backend - which is the proper way to do it?

I would like to get some variables from the backend (ie showTutorial) for the initial state in a reducer.
I was thinking about using just a call in axios from the reducer, but I am unsure if that is the best way of doing it.
This is how it is done right now:
import { UNSET_TUTORIAL, SET_FILTER_BLOCKS } from "../actions/types";
const initialState = {
showTutorial: true, // <-- instead of initializying to true, do the axios call
filterBlocks: "ALL"
};
export default (state = initialState, action) => {
switch (action.type) {
case UNSET_TUTORIAL:
return { ...state, showTutorial: false };
case SET_FILTER_BLOCKS:
return { ...state, filterBlocks: action.payload };
default:
return state;
}
};
I do not want to use redux-persist as I would like to understand first the proper way of doing it.
There is no difference between how you do this and how you update state from a backend for any other component.
You should have an action triggered on a top level app component (or any component created at application launch) that loads data from the backend and dispatches the action with the results from the backend. Your reducer then updates state based on this action.
The triggering of the action on your top level app component can be done using a lifecycle event when the component mounts, typically getDerivedStateFromProps()
The best way to do it is to call the relevant action from App.js, and render 'loading' until the data is properly fetched. Obviously, in case of error you will not continue to render components but display an error message.
Reducers should never call actions, this is breaking the redux flow.

Can I bind store's state with a component in react-redux?

I am Newbie in react-redux, I created a small CRUD application with react-redux, when I click over add button from a child component it takes data from all input boxes and updates my store's array with the help of Reducer.
const initialState = {
itemObjectArr:[] }
My Reducer updates this itemObjectArr, and send it to needy component with the help of connect(mapStateToProps)(NeedyChildComponent) function.
Now the real problem occurs when I try to print state.itemObjectArr on the console.
const mapStateToProps = (state)=>{
console.log(state.itemObjectArr);
return {itemObject:state.itemObjectArrs}}
Above code gives an error on console TypeError: Cannot read property 'itemObjectArrs' of undefined
and if I do console.log(state) it prints objects contained array easily without any error.
I searched for this error but not get anything important, I think this store's state is immutable and when it passes to mapStateToProps function as an argument then mapStateToProps checks changes in state and if it does not get any changes then it returns an error. maybe this is occurring here.
What does your reducer look like? Since the store is returned as undefined in your component, it sounds to me that you haven't defined a default case in your switch statement that returns the initial state before your update action is executed, like:
const myReducer = (state, action) => {
switch (action.type) {
case 'UPDATE_ITEM_OBJECT_ARR': return { itemObjectArr: [ 1, 2, 3] }
default:
return initialState;
}
}

React.js/Redux: setInterval and clearInterval in Reducers

Currently I am working on FCC Game of Life and I was able figure out how to generate the next app state (incrementally).
That being said, my next step is to figure out how to generate new app state (generation) continuously. In this case, I am trying to implement setInterval and clearInterval inside my reducer.
I'd like to have the ability to start/pause generating new generation (by toggling the state in state.start)
The thing I am having trouble of implementing it is the scope issue.
Here's part my reducer:
reducers/reducers_grid.js
export default function(state = initialState, action){
switch (action.type) {
....
case START:
let toggleStart = state.start === true ? false : true;
if (state.start === true){
console.log('START THE GAME')
const newGeneration = nextGeneration(action.payload,state)
return Object.assign({}, state, {cells: newGeneration, start: toggleStart })
}
else {
console.log('PAUSE THE GAME')
return Object.assign({}, state, {start: toggleStart })
}
return state;
}
Essentially, nextGeneration function results the new app state (generation) and updates it to via Object.assign
At first, I thought putting setInteveral inside the first if statement and clearInterval in the second if statement, but obviously that will create a scope issue.
It seems very trivial this part of logic, but I've been stuck on this awhile.
Don't do this in the reducer. Reducers should be pure functions (meaning no side-effects). Instead, you could create a component that is rendered only when the game is running (a boolean state which is set to true by the START action and false by the STOP action). Then in the component's componentDidMount function call setInterval with a function that dispatches a NEXT_GENERATION action. After that dispatch another action which adds the timer id to the store. Then, in the componentWillUnmount call clearInterval with the timer id.

Resources