vinay kumar
vinay kumar

Reputation: 593

React Redux how to update a object from array

I am very new to react and redux. I am trying to update state in reducer using below.

  export default function ReducersTodos(state = [], action) {
    switch (action.type) {
      case ADD_TODO:
                state = [...state, {
                        id: state.length? state.length+1: 1,
                        text: action.value,
                        like: 0
                      }]
                 return state
                 break;
      case ADD_LIKE:
              state = state.map(todo => todo.id === action.id ? { ...todo, like: todo.like+1 } : todo)
              return state
              break;
      case DIS_LIKE:
              state = state.map(todo => todo.id === action.id ? { ...todo, like: todo.like-1 } : todo)
              return state
              break;
      default:
        return state
    }
  }

Like Component

export class Like extends React.Component {
      constructor(props){
        super(props);
        // This binding is necessary to make `this` work in the callback
        this._handleClickAdd = this._handleClickAdd.bind(this);
        this._handleClickSub = this._handleClickSub.bind(this);
      }

      _handleClickAdd = function(e) {
        e.preventDefault();
        this.props.addLike(this.props.task.id);
      }
      _handleClickSub = function(e) {
        e.preventDefault();
        this.props.disLike(this.props.task.id);
      }
      render() {
          return (
                <div>
                  Like {this.props.task.like}
                  <button className="btn btn-primary" onClick={this._handleClickAdd} {...this.props.task.like}>+</button>
                  <button className="btn btn-danger" onClick={this._handleClickSub} {...this.props.task.like}>-</button>
                </div>
          );
      }
    }
    function mapDispatchToProps(dispatch) {
      return {addLike: bindActionCreators(addLike, dispatch),
              disLike: bindActionCreators(disLike, dispatch)}
    }
    //set props
    const mapStateToProps = (state, ownProps) => ({todos : state.todos});

    Like = connect(mapStateToProps, mapDispatchToProps)(Like)

When I am trying to hit like or dislike button. it re-rendered the whole list and UI look very weird. is there any way to update only part of the array without looking up into the whole array.

Working code available on GitHub https://github.com/vinaypost/todos

Upvotes: 0

Views: 1444

Answers (2)

Michael Peyper
Michael Peyper

Reputation: 6944

Wow, this was a real doozy. I cloned your repo, installed it, ran it, checked the logs it was pumping out, setup the redux dev tools and inspected the actions and the state changes, double checked the mapStateToProps, stared at it hoping I'd notice something, but I could not figure out what was going on. I could see that the state was coming in, and changing exactly as you wanted, but the ui was flipping around.

Eventually I found it here, nowhere near the reducer, or the mapStateToProps.

this.props.todos.reverse().map(todo =>
  <TodoSingleLi key={todo.id} task={todo}/>
)

The issue here is reverse(). From mdn:

The reverse method transposes the elements of the calling array object in place, mutating the array, and returning a reference to the array.

Basically, something other than the reducer was updating the state, and worse, not doing so immutably, which explains the levels of weirdness experienced.

Luckily, it's an easy fix:

this.props.todos.slice().reverse().map(todo =>
  <TodoSingleLi key={todo.id} task={todo}/>
)

Adding slice() into the chain allows the array to be safely mutated. From mdn:

slice does not alter the original array. It returns a shallow copy of elements from the original array.

Thanks for this one. It was nice to track down an honest-to-goodness bug for a change.

Upvotes: 2

Brian
Brian

Reputation: 1036

You can simplify your code with redux-auto.

Component:

import actions from 'redux-auto'

export class Like extends React.Component {

  render() {
      const id = this.props.task.id
      return (
            <div>
              Like {this.props.task.like}
              <button className="btn btn-primary" onClick={()=>actions.todos.like({id})} {...this.props.task.like}>+</button>
              <button className="btn btn-danger" onClick={()=>actions.todos.dislike({id})} {...this.props.task.like}>-</button>
            </div>
      );
  }
}
//set props
const mapStateToProps = (state, ownProps) => ({todos : state.todos});

Like = connect(mapStateToProps)(Like)

Reducers:

todos/add.js

export default function (todos, payload) {
  return [...todos, {  id: todos.length + 1,
                    text: payload.value,
                    like: 0 }];
}

todos/like.js

export default function (todos, payload) {
  return todos.map(todo => todo.id === payload.id ? { ...todo, like: todo.like+1 } : todo);
}

todos/dislike.js

export default function (todos, payload) {
  return todos.map(todo => todo.id === payload.id ? { ...todo, like: todo.like-1 } : todo);
}

To understand the above source. redux-auto automatically create actions and wires them to reduces based on the file structure. Where the folder name becomes the name of the property on the state. The files within a folder are actions to be performed on that part of the state.

Upvotes: -1

Related Questions