I'm trying to use React.useCallback
functions within React.useEffect
in order to avoid continuous re-rendering.
My objective is, once selected a sorting option, keep the list sorted even when new elements are added. But without continuous-rendering.
This is why I tried to use React.useCallback
But what I've done so far, doesn't prevent continuous re-rendering...
What's wrong with these React.useCallback
functions?
const memoizedIncrByName = React.useCallback(() => {
let new_data = [...data].sort((a, b) => {
if (b.name < a.name) return 1;
if (b.name > a.name) return -1;
return 0;
});
setData(new_data);
}, [data]);
const memoizedIncrByEmail = React.useCallback(() => {
let new_data = [...data].sort((a, b) => {
if (b.email < a.email) return 1;
if (b.email > a.email) return -1;
return 0;
});
setData(new_data);
}, [data]);
React.useEffect(() => {
console.log("SelectedOption:", selectedSortingOption);
if (selectedSortingOption !== null) {
if (selectedSortingOption.value === "name") {
memoizedIncrByName();
} else if (selectedSortingOption.value === "email") {
memoizedIncrByEmail();
}
}
}, [memoizedIncrByName, memoizedIncrByEmail, selectedSortingOption]);
<Select
defaultValue={selectedSortingOption}
onChange={SetSelectedSortingOption}
options={sortingOptions}
/>
data
sample:
let new_users = [
{
id: 5,
name: "Chelsey Dietrich",
username: "Kamren",
email: "[email protected]",
address: {
street: "Skiles Walks",
suite: "Suite 351",
city: "Roscoeview",
zipcode: "33263",
geo: {
lat: "-31.8129",
lng: "62.5342"
}
},
phone: "(254)954-1289",
website: "demarco.info",
company: {
name: "Keebler LLC",
catchPhrase: "User-centric fault-tolerant solution",
bs: "revolutionize end-to-end systems"
}
},
{
id: 6,
name: "Mrs. Dennis Schulist",
username: "Leopoldo_Corkery",
email: "[email protected]",
address: {
street: "Norberto Crossing",
suite: "Apt. 950",
city: "South Christy",
zipcode: "23505-1337",
geo: {
lat: "-71.4197",
lng: "71.7478"
}
},
phone: "1-477-935-8478 x6430",
website: "ola.org",
company: {
name: "Considine-Lockman",
catchPhrase: "Synchronised bottom-line interface",
bs: "e-enable innovative applications"
}
},
CodePudding user response:
const memoizedIncrByName = React.useCallback(() => {
let new_data = [...data].sort((a, b) => {
if (b.name < a.name) return 1;
if (b.name > a.name) return -1;
return 0;
});
setData(new_data);
}, [data]);
Every time the data changes, the memoization breaks. But every time you call the function, you change the data, thus breaking the memoization. And since your useEffect runs every time memoizedIncrByName or memoizedIncrByEmail changes, you get into a loop where you render, create a new memoizedIncrByName, run the effect, call memoizedIncrByName, which renders again and repeats the process.
If you want to keep the useEffect, i would recommend moving the functions inside of the effect and make the effect only depend on the sorting option:
React.useEffect(() => {
const incrByName = () => {
let new_data = [...data].sort((a, b) => {
if (b.name < a.name) return 1;
if (b.name > a.name) return -1;
return 0;
});
setData(new_data);
};
const incrByEmail = () => {
let new_data = [...data].sort((a, b) => {
if (b.email < a.email) return 1;
if (b.email > a.email) return -1;
return 0;
});
setData(new_data);
}
if (selectedSortingOption !== null) {
if (selectedSortingOption.value === "name") {
incrByName();
} else if (selectedSortingOption.value === "email") {
incrByEmail();
}
}
}, [selectedSortingOption]);
But i would actually recommend dropping the useEffect entirely. When you do the use effect approach, you end up having to render twice any time you want to change the sort: once to update the sort state, and then again when the useEffect updates the data state. Instead you can have the sorted list be a value that's calculated during rendering. For performance, you can wrap the calculation in a useMemo to skip the computation if nothing has changed:
const [data, setData] = useState(/* the full unsorted array */);
const [selectedSortingOption, setSelectedSortingOption] = useState(null);
const sortedData = useMemo(() => {
if (selectedSortingOption.value === "name") {
return [...data].sort((a, b) => {
if (b.name < a.name) return 1;
if (b.name > a.name) return -1;
return 0;
});
} else if (selectedSortingOption.value === "email") {
return [...data].sort((a, b) => {
if (b.email < a.email) return 1;
if (b.email > a.email) return -1;
return 0;
})
} else {
return data;
}
}, [data, selectedSortingOption]);
// Use sortedData below here
CodePudding user response:
From below suspect type of data
is array? (because you spread it for example)
const memoizedIncrByName = React.useCallback(() => {
let new_data = [...data].sort((a, b) => {
if (b.name < a.name) return 1;
if (b.name > a.name) return -1;
return 0;
});
setData(new_data);
}, [data]);
In that case each array is different from another, hence this function will be recreated when you set new state. e.g. [] !== []
What you could do is:
const memoizedIncrByName = React.useCallback(() => {
setData(ps => {
return [...ps].sort((a, b) => {
if (b.name < a.name) return 1;
if (b.name > a.name) return -1;
return 0;
});
});
}, []);
Same for the other function. Not sure what selectedSortingOption
is, but similarly you have to be careful with that (e.g. if it is object it will be recreated when you set state {} !=== {}
- you can look into useMemo
for it).