Home > Software engineering >  How do you update the React state of a nested object using UseReducer without the state values disap
How do you update the React state of a nested object using UseReducer without the state values disap

Time:12-26

In React, I have a " " button that is supposed to increment the value of an object property called "Strength" by one. Whenever I click the button, however, instead of increasing the value, all state values disappear on my browser view of the app. I don't know what I'm doing wrong. Here is my code below:

import { useReducer } from 'react';


function App(){
  const [state, dispatch] = useReducer(reducer, 
  {
    SPECIAL: {
      Strength: 5,
      Perception: 5,
      Endurance: 5,
      Charisma: 5,
      Intelligence: 5,
      Agility: 5,
      Luck: 5},
    Skills: {
      Barter: 3,
      Energy_Weapons: 3,
      Explosives: 3,
      Guns: 3,
      Lockpick: 3,
      Medicine: 3,
      Melee_Weapons: 3,
      Repair: 3,
      Science: 3,
      Sneak: 3,
      Speech: 3,
      Survival: 3,
      Unarmed: 3},
    Stats: {
      Action_Points: 65,
      Carry_Weight: 150,
      Critical_Chance: 0,
      Damage_Resistance: 0,
      Damage_Threshold: 0,
      Hit_Points: 100,
      Melee_Damage: 0,
      Rad_Resistance: 2,
      Skill_Rate: 0,
      Unarmed_Damage: 0}
  })

  function reducer(props){
    switch (props){
      case 'Strength_Inc':
        return Object.keys(state['SPECIAL']).map(SPECIAL => ({...SPECIAL, Strength: SPECIAL.Strength   1}))
      case 'Perception_Inc':
        return {...state.SPECIAL, Perception: state.SPECIAL.Perception   1}
      case 'Endurance_Inc':
        return {...state.SPECIAL, Endurance: state.SPECIAL.Endurance   1}
      case 'Charisma_Inc':
        return {...state.SPECIAL, Charisma: state.SPECIAL.Charisma   1}
      case 'Intelligence_Inc':
        return {...state.SPECIAL, Intelligence: state.SPECIAL.Intelligence   1}
      case 'Agility_Inc':
        return {...state.SPECIAL, Agility: state.SPECIAL.Agility   1}
      case 'Luck_Inc':
        return {...state.SPECIAL, Luck: state.SPECIAL.Luck   1}
      default:
        return state
    }
  }

  return (
    <div style={{
      color: "green",
      display: "block"
    }}>
      {Object.entries(state.SPECIAL).map(([key, value]) => (<p className='SPECIAL'> <button onClick={() => dispatch(key   '_Inc')}> </button> {value} <button onClick={() => dispatch(key   '_Dec')}>-</button> {key} </p>))}
      {Object.entries(state.Skills).map(([key, value]) => (<p className='Skills'> {value} {key} </p>))}
      {Object.entries(state.Stats).map(([key, value]) => (<p className='Stats'> {value} {key} </p>))}
      </div>
  )
};

export default App;

When I tried looking online for an answer all I found was a post that was made about three years ago which uses a class-based component as its answer. I was wondering how you would do this in a functional component.

CodePudding user response:

You have multiple problems with your reducer function.

  1. The reducer function gets two arguments: the first one represents the current state and the second one represents the action.
  2. You are not returning the full state in each case.

This is how your reducer function should look:

  function reducer(state, action) {
    switch (action) {
      case 'Strength_Inc':
        return {...state, SPECIAL: {...state.SPECIAL, Strength: state.SPECIAL.Strength   1}};
      case 'Perception_Inc':
        return {...state, SPECIAL: {...state.SPECIAL, Perception: state.SPECIAL.Perception  1}};
      case 'Endurance_Inc':
        return {...state, SPECIAL: {...state.SPECIAL, Endurance: state.SPECIAL.Endurance   1}};
      case 'Charisma_Inc':
        return {...state, SPECIAL: {...state.SPECIAL, Charisma: state.SPECIAL.Charisma   1}};
      case 'Intelligence_Inc':
        return {...state, SPECIAL: {...state.SPECIAL, Intelligence: state.SPECIAL.Intelligence   1}};
      case 'Agility_Inc':
        return {...state, SPECIAL: {...state.SPECIAL, Agility: state.SPECIAL.Agility   1}};
      case 'Luck_Inc':
        return {...state, SPECIAL: {...state.SPECIAL, Luck: state.SPECIAL.Luck   1}};
      default:
        return state;
    }
  }

I didn't actually test it but it should do the job.

CodePudding user response:

The reducer takes two parameters. The first is the state and the second is the action. The action parameter holds the value of your dispatch. Right now you only declare your reducer with one parameter (the state) and therefore your switch statement is trying to switch on the state object which is not what you want. See more here about using the useReducer: https://beta.reactjs.org/reference/react/useReducer

import { useReducer } from "react";

function reducer(props, action) {

  switch (action) {
    case "Strength_Inc":
      return {
        ...props,
        SPECIAL: {
          ...props.SPECIAL,
          Strength: props.SPECIAL.Strength   1
        }
      };
    case "Perception_Inc":
      return {
        ...props,
        SPECIAL: {
          ...props.SPECIAL,
          Perception: props.SPECIAL.Perception   1
        }
      };
    default:
      return props;
  }
}

function App() {
  const [state, dispatch] = useReducer(reducer, {
    SPECIAL: {
      Strength: 5,
      Perception: 5,
      Endurance: 5,
      Charisma: 5,
      Intelligence: 5,
      Agility: 5,
      Luck: 5
    },
    Skills: {
      Barter: 3,
      Energy_Weapons: 3,
      Explosives: 3,
      Guns: 3,
      Lockpick: 3,
      Medicine: 3,
      Melee_Weapons: 3,
      Repair: 3,
      Science: 3,
      Sneak: 3,
      Speech: 3,
      Survival: 3,
      Unarmed: 3
    },
    Stats: {
      Action_Points: 65,
      Carry_Weight: 150,
      Critical_Chance: 0,
      Damage_Resistance: 0,
      Damage_Threshold: 0,
      Hit_Points: 100,
      Melee_Damage: 0,
      Rad_Resistance: 2,
      Skill_Rate: 0,
      Unarmed_Damage: 0
    }
  });

  return (
    <div
      style={{
        color: "green",
        display: "block"
      }}
    >
      {state &&
        Object.entries(state.SPECIAL).map(([key, value]) => {
          return (
            <p className="SPECIAL">
              <button onClick={() => dispatch(key   "_Inc")}> </button>
              {value}
              <button onClick={() => dispatch(key   "_Dec")}>-</button>
              {key}
            </p>
          );
        })}
      {Object.entries(state.Skills).map(([key, value]) => (
        <p className="Skills">
          {" "}
          {value} {key}{" "}
        </p>
      ))}
      {Object.entries(state.Stats).map(([key, value]) => (
        <p className="Stats">
          {" "}
          {value} {key}{" "}
        </p>
      ))}
    </div>
  );
}

export default App;

I made a codesandbox example where i fixed the code: https://codesandbox.io/s/boring-hill-i66jvj?file=/src/App.js

  • Related