I am writing a code in react/react hooks that attempt to do the following.
Get an array of objects from the parent ponent as a prop
Set it as a state using useState
hook.
Sort the state according to the intended filters (Time and Rating), and re-render the child ponent.
What I see is that the below code updates the state after sorting, but even though the state is updated, the child ponent that is dependent on the state would not re-render. I thought the child ponent would automatically re-render whenever the state is changed?
import React, {useState} from 'react';
import ProfilePostspreview from './ProfilePostspreview';
function ProfileNavigation(props){
const [newarray, setnewarray]=useState(props.parray); //'parray' object passed as props and saved as 'newarray' state
const otc = () => { //Function to sort the state by time and set a new state
let k=newarray;
setnewarray(k.sort((a, b) => (a.time > b.time) ? -1 : 1 ));
}
const orc = () => { //Function to sort the state by rating and then time and set a new state
let k=newarray;
setnewarray(k.sort((a, b) => (a.rating > b.rating) ? -1 : (a.rating === b.rating) ? ((a.time > b.time) ? -1 : 1) : 1 ));
}
return (
<div>
<div className="sm_options"> //Component to trigger the otc and orc functions
<div className="sm_button" id="order_t" onClick={otc}>Sort by time</div>
<div className="sm_button" id="order_r" onClick={orc}>Sort by rating</div>
</div>
<div className="posts_preview_columns_p"> //This is dependent on the 'newarray' state but is not re-rendering even after the state is sorted and updated?
{newarray.map(na=>
<ProfilePostspreview
postThumbnail={na.photolink}
rating={na.rating}
time={na.time}
target={na.target}
/>
)}
</div>
</div>
);
}
export default ProfileNavigation;
What might be the reason for this? Is there something wrong with the code, or is sorting the state not considered powerful enough for React to re-render the child ponent? If latter is the case, what can be done to force re-render after sorting?
Any advice? Thanks!
I am writing a code in react/react hooks that attempt to do the following.
Get an array of objects from the parent ponent as a prop
Set it as a state using useState
hook.
Sort the state according to the intended filters (Time and Rating), and re-render the child ponent.
What I see is that the below code updates the state after sorting, but even though the state is updated, the child ponent that is dependent on the state would not re-render. I thought the child ponent would automatically re-render whenever the state is changed?
import React, {useState} from 'react';
import ProfilePostspreview from './ProfilePostspreview';
function ProfileNavigation(props){
const [newarray, setnewarray]=useState(props.parray); //'parray' object passed as props and saved as 'newarray' state
const otc = () => { //Function to sort the state by time and set a new state
let k=newarray;
setnewarray(k.sort((a, b) => (a.time > b.time) ? -1 : 1 ));
}
const orc = () => { //Function to sort the state by rating and then time and set a new state
let k=newarray;
setnewarray(k.sort((a, b) => (a.rating > b.rating) ? -1 : (a.rating === b.rating) ? ((a.time > b.time) ? -1 : 1) : 1 ));
}
return (
<div>
<div className="sm_options"> //Component to trigger the otc and orc functions
<div className="sm_button" id="order_t" onClick={otc}>Sort by time</div>
<div className="sm_button" id="order_r" onClick={orc}>Sort by rating</div>
</div>
<div className="posts_preview_columns_p"> //This is dependent on the 'newarray' state but is not re-rendering even after the state is sorted and updated?
{newarray.map(na=>
<ProfilePostspreview
postThumbnail={na.photolink}
rating={na.rating}
time={na.time}
target={na.target}
/>
)}
</div>
</div>
);
}
export default ProfileNavigation;
What might be the reason for this? Is there something wrong with the code, or is sorting the state not considered powerful enough for React to re-render the child ponent? If latter is the case, what can be done to force re-render after sorting?
Any advice? Thanks!
Share Improve this question edited Mar 19, 2020 at 14:53 keikai 15.2k10 gold badges55 silver badges72 bronze badges asked Mar 15, 2020 at 2:03 J.KoJ.Ko 8,8213 gold badges17 silver badges35 bronze badges3 Answers
Reset to default 7array::sort
The
sort()
method sorts the elements of an array in place and returns the sorted array. The default sort order is ascending, built upon converting the elements into strings, then paring their sequences of UTF-16 code units values.
What this means for you is the order of elements stored in an array may change, but the array is sorted in place which means the same array reference is returned (unlike other array functions that return new arrays).
React reconciliation occurs by examining state and props and makes a holistic assumption that if the next state/prop references haven't changed then the values haven't changed and thus returns the last puted rendered DOM. This is the important detail of updating react state... each update needs to reference a new object.
In your case you are merely saving the reference to the current array in state, mutating it, and resaving it. Since the reference is stable and doesn't change, react doesn't re-render.
const otc = () => {
let k = newarray; // <-- saved array reference!!
setnewarray(k.sort((a, b) => (a.time > b.time) ? -1 : 1 ));
}
The correct react way is to copy the current array values into a new array so it will have a new object reference.
const otc = () => {
const newSortedArray = [...newArray].sort(
(a, b) => (a.time > b.time) ? -1 : 1
); // spread old array values into new array, then sort
setNewArray(newSortedArray);
}
React checks for changes in props and state by doing a shallow object equality check. If you set the state to the same object you received from state, React assumes you aborted your change and doesn't do anything, even if the properties on the object changed.
The key is that the sort()
method sorts the array in place, and returns a reference to the same array. So React sees it as the same array object, even though the orser of its entries is different.
The solution is to create a new array:
let k = [...newarray];
When k
is passed to setnewarray
, React sees that it is an entirely different object, and triggers the rerender.
Sort()
work in place, which means you won't get what you want via directly return.
By the way, you can write the sort()
in a better format as below
const otc = () => {
const result = [...newarray];
result.sort((a, b) => b.time - a.time);
setNewarray(result);
};
const orc = () => {
const result = [...newarray];
result.sort((a, b) =>
a.rating !== b.rating ? b.rating - a.rating : b.time - a.time
);
setNewarray(result);
};
Try it online here: