Reputation: 465
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
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
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