Reputation: 593
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
}
}
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
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
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