Reputation: 93
I have this menu component where The menu contexts change based on the user input.
For example, If the user clicked RESERVATION button (fig 1) in the menu then to website body will change and the menu context will change to handle reservation features (fig2) <--- (desired behavior), however, the component stop re-render after the component mount.
fig1: | HOME | RESERVATION | ... | etc
fig2: | < | MAKE RESERVATION | ... | etc
What I tried :
It looks like the values in the store are changing and send to the component, too. I tried to use componentWillReceiveProps() but this did not work.
here is an example of my menu component:
import React, { Component } from 'react';
//redux thingy
import {connect} from 'react-redux';
class DefualtMenu extends Component {
render() {
return (
<div className="Defult-Menu-Main-Div">
<div className="box1">
<div className="box11" onClick={() => this.props.HandleMenuClick(this.props.MenuBoxes.box11)}>
<h3 className="boxh3" onClick={() => this.props.HandleMenuClick(this.props.MenuBoxes.box11)}>
{this.props.MenuBoxes.box11}
</h3>
</div>
</div>
</div>
);
}
}
const mapDispachToProps = (dispach) => {
return {
HandleMenuClick: (buttomName) => {
switch (buttomName){
case "RESERVATION":{
dispach({type: "HANDLE_BODY_CHANGE_RESERVATION"})
break;
}
}
}
}
}
const mapStateToProps = (state) => {
return {
MenuBoxes: state.MenuBoxes
}
}
export default connect(mapStateToProps, mapDispachToProps) (DefualtMenu);
Here how I handle the actions:
const reducer = (state = initial_state, action) => {
const new_state = {...state};
switch (action.type) {
}
case "HANDLE_BODY_CHANGE_RESERVATION": {
new_state.body = 'reservation';
if(new_state.userType == 'customer'){
new_state.MenuBoxes.box11 = '◄'
new_state.MenuBoxes.box12 = 'DELETE A \n RESERVATION'
new_state.MenuBoxes.box13 = ''
new_state.MenuBoxes.box21 = 'MAKE A \n RESERVATION'
new_state.MenuBoxes.box22 = 'REVIEW \n RESERVATIONS'
new_state.MenuBoxes.box23 = ''
new_state.MenuBoxes.box31 = 'UPDATE A \n RESERVATION'
new_state.MenuBoxes.box32 = ''
new_state.MenuBoxes.box33 = ''}
break;
}
return new_state;
};
I console.log my states: Here the state when I login to the admin account: the menu component did mount in this stage and everything is alright.
MenuBoxes:
box11: "HOME"
box12: "COUPONS"
box13: ""
box21: "RESERVATION"
box22: "ABOUT US"
box23: ""
box31: "INVENTORY"
box32: "SETTING"
box33: "SIGNOUT"
__proto__: Object
SingInfailureMessege: ""
body: "homepage"
isSignIn: true
showMenu: "default"
userType: "admin"
username: "admin"
When I click on RESERVATION -> The menu component already mount here and the state changed too but the component didn't re-render and change the props
MenuBoxes:
box11: "◄"
box12: "DELETE A ↵ RESERVATION"
box13: ""
box21: "MAKE A ↵ RESERVATION"
box22: "REVIEW ↵ RESERVATIONS"
box23: ""
box31: "UPDATE A ↵ RESERVATION"
box32: ""
box33: ""
__proto__: Object
SingInfailureMessege: ""
body: "reservation"
isSignIn: true
showMenu: "default"
userType: "admin"
username: "admin"
Upvotes: 3
Views: 1119
Reputation: 5707
const new_state = {...state};
is fine, it's the mutating of the MenuBoxes values that is an issue
Instead, you need to replace the nested objects and values from the top of state down the path to the final values that need to be changed.
const reducer = (state = initial_state, action) => {
switch (action.type) {
case "HANDLE_BODY_CHANGE_RESERVATION": {
return { // replace state object with a new object
...state, // copy state values to new object as references
MenuBoxes: { // replace MenuBoxes object with a new object
...state.MenuBoxes, // copy Menuboxes values to new object as references
box11 = '◄', // update box values
box12 = 'DELETE A \n RESERVATION',
box13 = '',
box21 = 'MAKE A \n RESERVATION',
box22 = 'REVIEW \n RESERVATIONS',
box23 = '',
box31 = 'UPDATE A \n RESERVATION',
box32 = '',
box33 = ''
}
}
}
};
In many cases where deep nesting is involved, using immer is a good option. It will do the above automatically for you.
import produce from "immer";
const reducer = (state = initial_state, action) => {
switch (action.type) {
case "HANDLE_BODY_CHANGE_RESERVATION": {
return produce(state, draft => {
draft.MenuBoxes.box11 = '◄'
draft.MenuBoxes.box12 = 'DELETE A \n RESERVATION'
draft.MenuBoxes.box13 = ''
draft.MenuBoxes.box21 = 'MAKE A \n RESERVATION'
draft.MenuBoxes.box22 = 'REVIEW \n RESERVATIONS'
draft.MenuBoxes.box23 = ''
draft.MenuBoxes.box31 = 'UPDATE A \n RESERVATION'
draft.MenuBoxes.box32 = ''
draft.MenuBoxes.box33 = ''}
});
};
Upvotes: 1
Reputation: 162
I disagree, you don't need any of these:
componentWillReceiveProps(nextProps) // maybe not this one
componentDidUpdate(prevProps) // probably this one
shouldComponentUpdate(nextProps, nextState) // possibly this one but not likely
I mean that's the purpose of react and redux, state changes -> checks if props change ->if yes re-renders. The functions above are useful for far more complicated cases then this one.
What is wrong, is the way your reducer is written. Please try going to something simpler in order to test. Please try this switch for starters:
switch (action.type) {
case "HANDLE_BODY_CHANGE_RESERVATION":
return { ...state, MenuBoxes: { box11: "test" } };
break;
default:
return state;
break;
}
I am sure it will work. Then start going to more complicated.
Finally i am speculating the mistake is in the way you handle state in the reducer. Never do this:
const new_state = {...state};
By doing this, you directly mutate the state further below (we never do that).
The way you should do it, is create a new object which includes all the changes of the state:
let new_state = {};
...
new_state.body = "reservation";
new_state.MenuBoxes = {};
new_state.MenuBoxes.box12 = "DELETE A \n RESERVATION";
and in the end return the previous state along with the changes like this:
return { ...state, ...new_state };
I am no expert but i think, right now you mutate state so new state = old state => no re-renders needed
Upvotes: 1
Reputation: 748
You need something that is going to check for a change in props and update the component manually. like
componentWillReceiveProps(nextProps) // maybe not this one
componentDidUpdate(prevProps) // probably this one
shouldComponentUpdate(nextProps, nextState) // possibly this one but not likely
I made a codesandbox example that helped someone with updating their React component from a Redux change. I used componentDidUpdate() in that case.
https://codesandbox.io/s/88xyxjm30l
Upvotes: 0