Scaccoman
Scaccoman

Reputation: 465

React/Redux - Component State not updating

It seems like I have come across a very common problem in React/Redux. There's a multitude of people having this exact issue, still the cause can vary by a lot so I couldn't identify a possible solution. I'm trying to build a webpage with a list that can be re-ordered by pressing the up and down buttons.

I have managed to track down the updated state (array) until the reducer but the component is not updating. I believe I'm not correctly updating the component's state.

Here's my component:

import React, { Component } from 'react';
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { changeOrder } from "../actions/index.js";

// let list = ["apple", "banana", "orange", "mango", "passion fruit"];

export class Home extends Component {
  constructor(props){
    super(props);
    this.renderList = this.renderList.bind(this);
  }

  goUp(position){
    this.props.changeOrder(position, "up");
  }

  goDown(position){
    this.props.changeOrder(position, "down");
  }

  renderList(fruit){
    const position = this.props.list.indexOf(fruit);
    return (
          <div key={fruit}>
            <li>{fruit}</li>
            <button onClick={() => this.goUp(position)}>Up</button>
            <button onClick={() => this.goDown(position)}>Down</button>
          </div>
        );
  }

  render() {
      return (
        <div>
          <h1>This is the home component</h1>
          <ol>
            {this.props.list.map(this.renderList)}
          </ol>
        </div>
      );
  }
}

function mapStateToProps(state){
  console.log(state);
    return { list: state.list };
}

function mapDispachToProps(dispatch) {
    return bindActionCreators({ changeOrder }, dispatch);
}

export default connect(mapStateToProps, mapDispachToProps)(Home);

Action:

export const GO_UP = "GO_UP";
export const GO_DOWN = "GO_DOWN";

export function changeOrder(position, direction) {
    console.log("position: " + position + " | direction: " + direction);
   switch (direction) {
       case "up": {
           return {
                type: GO_UP,
                payload: position
           };
       }
       case "down": {
           return {
               type: GO_DOWN,
               payload: position
           };
       }
   }
}

Reducer:

import { GO_UP, GO_DOWN } from "../actions/index.js";
let list = ["apple", "banana", "orange", "mango", "passion fruit"];

function arrayMove(arr, old_index, new_index) {
    if (new_index >= arr.length) {
        var k = new_index - arr.length + 1;
        while (k--) {
            arr.push(undefined);
        }
    }
    arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
    return arr; // for testing
}

export default function (state = list, action){
    // debugger;
    switch (action.type){
        case GO_UP: {
            let newState = arrayMove(state, action.payload, action.payload -1);
            console.log(newState);
            return newState;
        }
        case GO_DOWN: {
            let newState = arrayMove(state, action.payload, action.payload +1);
            console.log(newState);
            return newState;
        }
        default: return list;
    }
}

Here's the reducer's Index.js:

import { combineReducers } from 'redux';
import ReducerList from "./reducer_list";

const rootReducer = combineReducers({
  list: ReducerList
});

console.log(rootReducer);

export default rootReducer;

And lastly index.js:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import ReduxPromise from "redux-promise";

import App from './components/app';
import reducers from './reducers';

const createStoreWithMiddleware = applyMiddleware(ReduxPromise)(createStore);

ReactDOM.render(
  <Provider store={createStoreWithMiddleware(reducers)}>
    <App />
  </Provider>
  , document.querySelector('.container'));

Any help would be greatly appreciated!

Upvotes: 0

Views: 1853

Answers (2)

steppefox
steppefox

Reputation: 1844

The problem is located in your reducer in the function arrayMove(). You're changing existing array named arr. After the change of it, React component doesn't know that property state.list actually changed, because the memory reference is following the same old array (with changed content).

To tell your application, that list is actually changed, you need to return NEW array. Like after all your logic in arrayMove, at the end you need to return something like:

return [].concat(arr);

In the functional programming terminology, your arrayMove is a dirty function with side effects, because it's mutating existing object/array by it's reference. The "clean" function should return a new instance of an object.

Upvotes: 1

Skyler
Skyler

Reputation: 656

When you are using react-redux you want to make sure you do not mutate state, rather you need to create a new state object (a shallow copy). Refer here for more information. Redux will not know if the state has updated if you mutate the previous state.

in arrayMove you are mutating the current state. instead of

arrayMove(state, action.payload, action.payload -1);

use the following,

arrayMove(state.slice(), action.payload, action.payload -1);

Upvotes: 3

Related Questions