Redux not updating state correctly — state stays as empty array despite adding an object in the line prior

旧巷老猫 提交于 2020-03-05 06:05:53

问题


I'm trying to update my React Redux state using the usual practice. You immutably copy your current state into the return function and then overwrite the properties which are to have a new value.

My expected result is to have

state.copy

update to

[
    { webCopy: "", id: 0 },
    { webCopy: "", id: 1 }
]

Unfortunately, it's an empty array despite returning the copy value as a new array with a JS object inside.

I've tried looking at StackOverflow search results for "react redux state not updating correctly" but none of the search results seem to match my situation.

In my app I've even checked: Only

case actionTypes.ADD_COMPONENT

(the first one in the reducer) activates at this part of the app. It is literally a button click which triggers

ADD_COMPONENT

on its own. There is no activity from any other reducer that could be overwriting my state with an empty [] array.

Why am I ending up with an empty array at the end of case actionTypes.ADD_COMPONENT?

My console.log() statement even shows a JavaScript Object being the value for newCopy RIGHT BEFORE I RETURN a new state.

So here is reducer.js. I've uploaded the COMPLETE reducer instead of just case actionTypes.ADD_COMPONENT where the error is taking place:

import * as actionTypes from "./constants";

let component = "";
let idIteration = null;
let stateArray = [];
let tempCopy = [];

const initialState = {
  components: [],
  uniqueIdCounter: 0,
  currentPage: 1,
  copy: [],
  siteURL: "/salespage/"
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    // you can probably ignore everything outside of this switch statement
    // this is the important switch statement
    case actionTypes.ADD_COMPONENT:
      stateArray = [...state.components];
      console.log("state:", state);
      console.log("State Copy:", state.copy);
      component = action.payload[0]; // will be "Header", "Headline", "Text Area", "Image", "Email Field", "Footer"
      if (component === "Header") {
        // append "Header" component to the beginning of the list
        stateArray.unshift({
          type: component,
          id: state.uniqueIdCounter
        });
      } else {
        // push component to the end of the list
        stateArray.push({
          type: component,
          id: state.uniqueIdCounter
        });
      }
      idIteration = state.uniqueIdCounter + 1;

      // SEND HELP. How could state.copy possibly be anything other than a JS object after these lines?
      let newCopy = [...state.copy];
      newCopy.push({ webCopy: "", id: action.payload[1] });
      console.log("TTTTTTTTT", newCopy);

      return {
        ...state,
        components: stateArray,
        uniqueIdCounter: idIteration,
        copy: newCopy // why doesn't this update the array to contain newCopy? it should.
      };

    // i don't know how any of this could possibly cause my state.copy to be equal to []
    case actionTypes.SET_NEW:
      console.log("Activating SET_NEW");
      // uploads the newly reordered set of components to state
      let uploadNewOrder = [...action.payload];
      return {
        ...state,
        components: uploadNewOrder
      };
    case actionTypes.DEL:
      console.log("activating DEL");
      // uploads the state less the deleted item
      let uploadShortened = [...action.payload];
      return {
        ...state,
        components: uploadShortened
      };
    case actionTypes.PAGE_CHANGE:
      console.log("activating PAGE_CHANGE");

      stateArray = [...state.components];
      return {
        ...state,
        // action.payload is set in each page's ComponentDidMount()
        currentPage: action.payload
      };
    case actionTypes.NEW_VAR:
      console.log("activating NEW_VAR");
      // case "NEW_VAR" fires from Customize's renderStateComponents()
      stateArray = [...state.components];
      tempCopy = Object.assign([], state.copy); // avoids the TypeError bug with [...state.copy]
      // push an empty copy datapoint to state with a unique id to use in identifying which copy goes where in interface
      let newInputFieldNumber = { webCopy: "", id: action.payload };
      tempCopy.push(newInputFieldNumber);
      return {
        ...state,
        components: stateArray,
        copy: tempCopy
      };
    case actionTypes.ADD_COPY:
      console.log("activating ADD_COPY");

      tempCopy = [...state.copy]; // immutably copy state.copy
      let textToAdd = action.payload[0];
      let indexToFind = action.payload[1];

      for (let i = 0; i < tempCopy.length; i++) {
        if (tempCopy[i].id === indexToFind) {
          // Modify the JS object linked to the appropriate input field
          tempCopy[i] = { webCopy: textToAdd, id: indexToFind };
        }
      }
      return {
        ...state,
        components: stateArray,
        copy: tempCopy
      };
    case actionTypes.SET_URL:
      console.log("activating SET_URL");

      stateArray = [...state.components];
      // TODO: handle cases like user entered www.etc.com and https://www.test.com
      let domain = action.payload;
      const notAllowed = [
        "https://www.",
        "www.",
        ".",
        "www",
        "com",
        "net",
        "org",
        ".com",
        ".net",
        ".org"
      ];
      for (let i = 0; i < notAllowed.length; i++) {
        if (domain.includes(notAllowed[i])) {
          domain = domain.replace(notAllowed[i], "");
        }
      }
      return {
        ...state,
        components: stateArray,
        siteURL: "/salespage/" + domain
      };

    default:
      return state;
  }
};

export default reducer;

Any suggestions what to try? I tried adding this case to my reducer and activating it in the line after .ADD_COMPONENT and it's still yielding an empty array:

case actionTypes.PREP_COPY:

            let prepCopy = [...state.copy];
            prepCopy.push({ webCopy: "", id: action.payload });
            return {
                ...state,
                copy: prepCopy
            };

I gave the offending variable, newCopy, a unique name as to not use global scope. In case that matters somehow.

What other code could I show? Only a reducer can affect Redux state and there is no other code running besides .ADD_COMPONENT and (now) .PREP_COPY

EDIT: As per suggestions, I've tried using the spread operator with my variables while returning state. The code now works if I use the Spread operator in BOTH Reducer Actions only. Using it in just one of them still yields an empty array. Like so:

const reducer = (state = initialState, action) => {
    switch (action.type) {
        case actionTypes.ADD_COMPONENT:
            // edited out some code...
            let newCopy = [...state.copy];
            newCopy.push({ webCopy: "", id: action.payload[1] });
            console.log("newCopy", newCopy);

            return {
                ...state,
                components: stateArray,
                uniqueIdCounter: idIteration,
                copy: [...newCopy]
            };

        case actionTypes.PREP_COPY:
            console.log("State.copy:", state.copy);
            let prepCopy = [...state.copy];
            prepCopy.push({ webCopy: "", id: action.payload });
            console.log("PREPCOPY:", prepCopy);
            return {
                ...state,
                copy: [...prepCopy]
            };

So either I use both those Actions at once, or nada. Literally: When I use both, I get two JS Objects added per cycle. When I use only one, I get 0 JS objects added per cycle. Wut.

Like, should I report a bug to React's team?

2nd edit: here is a fully working code sandbox. Check the console.log statements when you click a button https://codesandbox.io/s/lucid-heisenberg-iczww


回答1:


The problem is in Pallette.js in your codesandbox link. On line 29, you call this.props.copy.pop(), which removes the last element from copy. props.copy is the same array as state.copy, so you're mutating state every time addComponent is called. If you change this:

nextCopyId = this.props.copy.pop().id + 1;

to this:

nextCopyId = this.props.copy[this.props.copy.length - 1].id + 1;

or this:

nextCopyId = [...this.props.copy].pop().id + 1;

state.copy is no longer an empty array.

In general when working with React and Redux, make sure to avoid mutative methods as much as possible. Prefer concat over push or unshift. Prefer map, filter, or reduce (which all return a copy of the array) over forEach or a for loop (which encourages you to mutate the array). Sometimes mutation is unavoidable, but be cautious and aware that when you mutate arrays/objects coming from props, you're affecting the state as well!




回答2:


Have you tried returning copy as a new array? It's solved problems for me before

You can just use the spread operator to create it:

  return {
    ...state,
    components: stateArray,
    uniqueIdCounter: idIteration,
    copy: [...newCopy]
  };

Another, similar method would eliminate the need to push the value onto a temporary variable:

  return {
    ...state,
    components: stateArray,
    uniqueIdCounter: idIteration,
    copy: [...state.copy, { webCopy: "", id: action.payload[1] }]
  };


来源:https://stackoverflow.com/questions/60031869/redux-not-updating-state-correctly-state-stays-as-empty-array-despite-adding

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!