I have RowsCont
with renderRows
renderRows = () => {
let result = [];
const {rows} = this.props;
console.log('RENDER');
console.log(rows);
for(let key in rows){
result.push(<Row={rows[key].id}
onHover={this.onHover}
row={rows[key]}/>);
}
return result;
};
....
render(){<div>{this.renderRows()}</div>}
....
function mapStateToProps(state) {
console.log('mapState');
console.log(state.rows.rows);
return {
rows: state.rows.rows,
}
}
Row Component:
render(){
return (
<div>
<div>{this.props.row.id}</div>
<div>{this.props.row.value}</div>
</div>
)
}
Rows is a collection of objects, each like this: {id: 5, value: 'some value'}
I have reducer, which changes the value of an object with specific id:
case 'SET_NEW_ROW_VALUE':
let currentRows = state.rows;
const changedIndex = currentRows .findIndex((obj) => obj.id === action.id);
currentRows [changedIndex].value = action.value;
return Object.assign({}, state, {
rows: currentRows
});
Everything works, but, if state is changed, the ponent doesn't re-render.
I am sure that state is changed, because I see the new state in mapStateToProps
with console.log
, every time when it is changed, but I don't see console.log
from renderRows
.
Each row ponent has and onHover
. If I hover the row, it is re-rendered and I see the new state for the row with the new value.
I have RowsCont
with renderRows
renderRows = () => {
let result = [];
const {rows} = this.props;
console.log('RENDER');
console.log(rows);
for(let key in rows){
result.push(<Row={rows[key].id}
onHover={this.onHover}
row={rows[key]}/>);
}
return result;
};
....
render(){<div>{this.renderRows()}</div>}
....
function mapStateToProps(state) {
console.log('mapState');
console.log(state.rows.rows);
return {
rows: state.rows.rows,
}
}
Row Component:
render(){
return (
<div>
<div>{this.props.row.id}</div>
<div>{this.props.row.value}</div>
</div>
)
}
Rows is a collection of objects, each like this: {id: 5, value: 'some value'}
I have reducer, which changes the value of an object with specific id:
case 'SET_NEW_ROW_VALUE':
let currentRows = state.rows;
const changedIndex = currentRows .findIndex((obj) => obj.id === action.id);
currentRows [changedIndex].value = action.value;
return Object.assign({}, state, {
rows: currentRows
});
Everything works, but, if state is changed, the ponent doesn't re-render.
I am sure that state is changed, because I see the new state in mapStateToProps
with console.log
, every time when it is changed, but I don't see console.log
from renderRows
.
Each row ponent has and onHover
. If I hover the row, it is re-rendered and I see the new state for the row with the new value.
-
1
In this line
result.push(<Rowkey={row[key].id}
, I notices that it should berows
instead ofrow
should it beresult.push(<Rowkey={rows[key].id}
– acarlstein Commented May 4, 2019 at 20:24 -
Yes, it is
rows
. * Edited. – gdfgdfg Commented May 4, 2019 at 20:29
2 Answers
Reset to default 3Your ponent is not rerendering because your object is the same as it was before
In a rendering cycle, React does a diff check on the new ining props. If anything is different, it re-renders. In your case, the object is the same object as before.
As an example:
var rows = {id: 1, value: 'someVal'};
var oldRows = rows;
rows.id = 2;
rows.value = 'someNewVal';
console.log(rows === oldRows); // => true
React does a diff check between the rows
and the oldRows
and sees that the object itself is the same! Since the diff check sees equality, no re-rendering occurs.
The easiest way around this is to update prop values by their primitives within the object.
For example this will force a rerendering cycle when a change occurs:
function mapStateToProps(state) {
console.log('mapState');
console.log(state.rows.rows);
return {
id: state.rows.rows.id,
value: state.rows.rows.value
}
}
If an object is absolutely necessary to be passed to your ponent, you can create a new object in your redux stores. Something like state.rows = Object.assign({}, oldRows)
should do the trick.
in redux you should never mutate the state, so currentRows[changedIndex].value = action.value;
is forbidden
Here is a possible solution :
case 'SET_NEW_ROW_VALUE':
const rows = state.rows.map(
(row, index) => row.id !== action.id ?
row :
{...row, value: action.value}
);
return {...state, rows};
In your solution, you don't have a new reference for the prop state.rows
after executing the case SET_NEW_ROW_VALUE
. As react-redux
use shallow parison
(===
operator) to detect if an object has changed, with your solution the ponent will not be re-rendered as state.rows
will be the same object (same reference) than before