47

Bear with me here as this question pertains to my first test app using either React, Redux or react-redux. Docs have gotten me far and I have a mock banking app that mostly works. My state object looks roughly like this:

{
 activePageId: "checking",
 accounts: [
  checking: {
    balance: 123,
    transactions: [
      {date, amount, description, balance}
    ]
  }
 ]
}

I have just two actions:

1. CHANGE_HASH (as in url hash). This action always works as expected and all the reducer does is update the state.activePageId (yes, I'm cloning the state object and not modifying it). After the action, I can see the state has changed in the Redux store and I can see that React has updated.

function changeHash(id) {
        return {
            type: "CHANGE_HASH",
            id: id
        }
}

2. ADD_TRANSACTION (form submission). This action never updates React, but it always updates the Redux store. The reducer for this action is updating state.accounts[0].balance and it's adding a transaction object to the array state.accounts[0].transactions. I don't receive any errors, React just doesn't update. HOWEVER, if I dispatch a CHANGE_HASH action React will catch up and display all of the ADD_TRANSACTION state updates properly.

function addTransaction(transaction, balance, account) {
    return {
        type: "ADD_TRANSACTION",
        payload: {
            transaction: transaction,
            balance: balance,
            account: account
        }
    }
}

My reducer...

    function bankApp(state, action) {
        switch(action.type) {
            case "CHANGE_HASH":
                return Object.assign({}, state, {
                    activePageId: action.id
                });
            case "ADD_TRANSACTION":
            // get a ref to the account
                for (var i = 0; i < state.accounts.length; i++) {
                    if (state.accounts[i].name == action.payload.account) {
                        var accountIndex = i;
                        break;
                    }
                }

            // is something wrong?
                if (accountIndex == undefined)  {
                    console.error("could not determine account for transaction");
                    return state;
                }

            // clone the state
                var newState = Object.assign({}, state);

            // add the new transaction
                newState.accounts[accountIndex].transactions.unshift(action.payload.transaction);

            // update account balance
                newState.accounts[accountIndex].balance = action.payload.balance;

                return newState;
            default:
                return state;
        }

My mapStateToProps

    function select(state) {
        return state;
    }

What am I missing here? I'm under the impression that React is supposed to update as the Redux storeis updated.

Github repo:

Deployment bank demo

p.s. I lied about not having any errors. I do have a number of warnings

""Warning: Each child in an array or iterator should have a unique "key" prop..."

I'm already giving them a key prop set to it's index. I doubt that has anything to do with my issue though.

Nompumelelo
  • 929
  • 3
  • 17
  • 28
curiouser
  • 970
  • 2
  • 12
  • 22
  • 1
    Hmm, I think two things will help to narrow down the bug: 1) use [redux-logger](https://github.com/fcomb/redux-logger) to see what's changed when action happens 2) post your reducer, action and mapStateToProps code. – Cheng Oct 15 '15 at 09:26
  • Unfortunately, I wasn't able to get redux-logger working, but I've logged things out on my own and things are working as expected there. I'll update the question now with reducer, actions and mapStateToProps. – curiouser Oct 15 '15 at 15:32
  • How does React determine if the state has changed? Perhaps because the change is fairly deep in the state object and the change is so small it's not re-rendering? How does react-redux decide whether when to update React props? I just logged out the value of state in mapStateToProps and it's updated there, just not in React. – curiouser Oct 16 '15 at 04:22
  • 3
    React Redux uses a referential equality check. If you mutate the state, it won't see the changes. Don't mutate the state. – Dan Abramov Oct 16 '15 at 10:20

1 Answers1

60

The problem is in this piece of code:

// clone the state
var newState = Object.assign({}, state);         

// add the new transaction

newState.accounts[accountIndex].transactions.unshift(action.payload.transaction);

// update account balance
newState.accounts[accountIndex].balance = action.payload.balance;

Cloning the state object doesn't mean you can mutate the objects it is referring to. I suggest you to read more about immutability because this isn't how it works.

This problem and solution to it are described in detail in Redux “Troubleshooting” docs so I suggest you to read them.

https://redux.js.org/troubleshooting

I also suggest you to take a look at Shopping Card example in Flux Comparison for Redux because it shows how to update nested objects without mutating them in a similar way to what you are asking.

https://github.com/voronianski/flux-comparison/tree/master/redux

Muhammad Usman
  • 10,039
  • 22
  • 39
Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
  • 9
    Thank you! I had read through those docs and had attempted to preserve the state object, however my misunderstanding is in the Object.assign method. While this was creating a new object from the initial object, it was only a shallow clone meaning objects nested in the state object were not cloned but still referencing the original state objects nested objects. I wasn't running into this property with another action because no nested objects were being modified. A good write-up on object cloning (#5 is what got me): http://blog.soulserv.net/understanding-object-cloning-in-javascript-part-i/ – curiouser Nov 12 '15 at 03:39
  • 7
    That's correct. In general you don't want to deeply clone everything though because it is expensive. Just clone the parts you change. Split reducers into many functions to keep the code maintainable. You can take inspiration from "shopping-cart" example in Redux repo. – Dan Abramov Nov 12 '15 at 23:16