Too many re-renders when setting state - reactjs

Seems like this is a common issue in React, so apologies for asking this question again. I have a ParentComponent that contains a map with state. The map needs to have state because you can update its contents are meant to be updated. When I try to set the state of the map, I get an error stating too many re-renders. I've even tried using the useEffect hook, but I can't figure out the issue. Here's what I have:
const ParentComponent = ({show, onCloseModalButton}) => {
const [resorts, setResorts] = React.useState(new Map())
const tmpResorts = new Map(resorts)
tmpResorts.set("Keystone", [39.6069742, -105.97011])
tmpResorts.set("Breckenridge", [39.4808, -106.0676])
tmpResorts.set("Vail", [39.6061, -106.3550])
tmpResorts.set("Crested Butte", [38.8991, -106.9658])
tmpResorts.set("Winter Park", [39.8841, -105.7627])
tmpResorts.set("Copper Mountain", [39.5022, -106.1497])
setResorts(tmpResorts)
...
Here's what I've tried, to no avail:
const ParentComponent = ({show, onCloseModalButton}) => {
const [resorts, setResorts] = React.useState(new Map())
const tmpResorts = new Map(resorts)
React.useEffect(() => {
tmpResorts.set("Keystone", [39.6069742, -105.97011])
tmpResorts.set("Breckenridge", [39.4808, -106.0676])
tmpResorts.set("Vail", [39.6061, -106.3550])
tmpResorts.set("Crested Butte", [38.8991, -106.9658])
tmpResorts.set("Winter Park", [39.8841, -105.7627])
tmpResorts.set("Copper Mountain", [39.5022, -106.1497])
setResorts(tmpResorts)
}, [tmpResorts])
...
Appreciate any help!

Remove tmpResorts from useEffect Dependency array will fix it:
const [resorts, setResorts] = useState(new Map());
useEffect(() => {
console.log('called');
const tmpResorts = new Map(resorts);
tmpResorts.set('Keystone', [39.6069742, -105.97011]);
tmpResorts.set('Breckenridge', [39.4808, -106.0676]);
tmpResorts.set('Vail', [39.6061, -106.355]);
tmpResorts.set('Crested Butte', [38.8991, -106.9658]);
tmpResorts.set('Winter Park', [39.8841, -105.7627]);
tmpResorts.set('Copper Mountain', [39.5022, -106.1497]);
setResorts(tmpResorts);
}, []);

Related

custom hook create infinite loop

This hook create an inifite loop. I don't understnd why, since my dependencies array is set.
Error : Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
Custom hook :
export const useListGuessers = () => {
const [list, setList] = useState([]);
const hasMarketing = UserHelper.hasAuthorization(AUTHORIZATION_MARKETING);
const hasTechnical = UserHelper.hasAuthorization(AUTHORIZATION_TECHNICAL);
const dashboardGroups = new DashboardGroups({hasMarketing, hasTechnical});
const guessers = [
...dashboardGroups.appProductGroup(),
...dashboardGroups.articlesGroup(),
...dashboardGroups.mediasGroup(),
...dashboardGroups.productsGroup(),
...dashboardGroups.orderableProductsGroup(),
...dashboardGroups.typesGroup(),
...dashboardGroups.usersGroup(),
...dashboardGroups.othersGroup(),
...dashboardGroups.userManagementGroup(),
];
const filteredGuesser = guessers
.filter(({canShow}) => canShow)
.map((guesser: any) => {
return {
label: guesser.label ?? guesser.value.options.label,
link: (guesser.operation && `user-management/${guesser.operation}`) ?? guesser.value.name,
};
})
.sort((a, b) => a.label.localeCompare(b.label));
useEffect(() => {
filteredGuesser && setList(filteredGuesser);
}, [filteredGuesser]);
return list;
};
The class :
export class DashboardGroups {
authorizations: {hasMarketing: boolean; hasTechnical: boolean};
constructor(authorizations: {hasMarketing: boolean; hasTechnical: boolean}) {
this.authorizations = authorizations;
}
// [all groups comes here...]
getGroups = () => {
// return an object for each groups with labels, and the group as "children"
};
}
Since filteredGuesser calculates on each re-render, which triggers useEffect(..., [filterGuesser] which causes re-render... so it loops.
The easiest straighforward solution is to ensure reference equality for filteredGuesser with useMemo. Then it will be referentially the same until guessers is changed:
const filteredGuesser = useMemo(() =>
guessers
.filter(({canShow}) => canShow)
.map((guesser: any) => {
label: guesser.label ?? guesser.value.options.label,
link: (guesser.operation && `user-management/${guesser.operation}`) ?? guesser.value.name,
})
.sort((a, b) => a.label.localeCompare(b.label))
, [guessers]);
However, I think the better solution would be reconsider need in
useEffect(() =>
...
setList(filteredGuesser)
This storing ready for use calculation into state does not seem reasonable to me. I think you better use filteredGuesser directly, instead of storing it into list state
Beta docs for useMemo
Referential equality aka strict equality on MDN

How to inform useCallback that useState was updated?

I have problem with me code because in useCallback function always is shown state outdated, pre-last one.
In useEffect(()) I do console.log, and I can see the change of state, but in second function it shown only when it is click two times. It is not the way I can do something.
import produce from Immer;
const [obiektPompa, setObiektPompa] = useState([]);
const [modulPV, setModulPV] = useState([]);
function setDodatkowe(e, index) {
e.preventDefault();
const {cena, nazwa} = formData.current;
const newProduct = {nazwa: nazwa.value,
cena: parseFloat(cena.value)
}
setObiektPompa((prevState) => ([...prevState, newProduct ]))
console.log("zmiana Pomp", obiektPompa)
AddItemPrice(index)
}
useEffect(() => {
console.log("obiekt Pompa z useEffect", obiektPompa) //Console.log state
}, [obiektPompa])
const AddItemPrice = useCallback ((index ) => {
setModulyPV(
produce( modulyPV, draft => {
const objIndex = draft.findIndex((obj => obj.index === index));
const value = obiektPompa[obiektPompa.length -1].cena
console.log("index", draft[index].cena)
console.log("Vaalue pompa", value)
})
)
}, [modulyPV, setModulyPV, setDodatkowe, setObiektPompa, obiektPompa])
Needless to say that I put useRef hook the value from setDodatkowe is from submit property of button.
Problem is with obiektPompa which is useState, and as you see in useEffect show me everything, but in useCallback do not.
In AddItemPrice I tried to add dependence formData.current.onsubmit but it do not give result.

React useState is not re-rendering while it is referring cloned array by spreading

I am trying to sort an array and reflect its sort result immediately by useState hook.
I already new that react is detecting its state change by Object.is(), so trying to spread array before using useState like below;
const [reviews, setReviews] = useState([...book.reviews]);
useEffect(() => {
const sortedReviews = [...reviews]
.sort((review1: IReview, review2: IReview) =>
sortBy(review1, review2, sortRule));
setReviews([...sortedReviews])
}, [sortRule])
After sorting I checked the value of variable sortedReviews and it was sorted as expected, but react did not re-render the page so this sorting was not reflected to UI.
I already searched solutions and it seemed many could solve the issue by spreading an array to before calling useState like this stack overflow is explaining: Why is useState not triggering re-render?.
However, on my end it is not working.. Any help will be very appreciated. Thank you!
[Added]
And my rendering part is like below;
<>
{
sortedReviews
.map((review: IReview) => (
<ReviewBlock id={review.id}
review={review}
targetBook={book}
setTargetBook={setBook}/>
))
}
</>
Sometimes I facing this issue too, in my case I just "force" render calling callback.
First solution:
const [reviews, setReviews] = useState([...book.reviews]);
useEffect(() => {
const sortedReviews = [...reviews]
.sort((review1: IReview, review2: IReview) =>
sortBy(review1, review2, sortRule));
setReviews(()=> [...sortedReviews]) // <-- Here
}, [sortRule])
second solution:
You can use useRef to get data in real time, see below:
const [reviews, setReviews] = useState([...book.reviews]);
const reviewsRef = useRef();
useEffect(() => {
const sortedReviews = [...reviews]
.sort((review1: IReview, review2: IReview) =>
sortBy(review1, review2, sortRule));
setReviewRef([...sortedReviews])
}, [sortRule])
function setReviewRef(data){
setReview(data);
reviewsRef.current = data;
}
So, instead use the state reviews use reviewsRef.current as u array
I hope you can solve this!
When components don't re-render it is almost always due to mutations of state. Here you are calling a mutating operation .sort on reviews. You would need to spread the array before you mutate it.
useEffect(() => {
const sortedReviews = [...reviews].sort((review1: IReview, review2: IReview) =>
sortBy(review1, review2, sortRule)
);
setReviews(sortedReviews);
}, [sortRule]);
But there are other issues here. reviews is not a dependency of the useEffect so we would want to use a setReviews callback like setReviews(current => [...current].sort(....
In general sorted data makes more sense as a useMemo than a useState. The sortRule is a state and the sortedReviews are derived from it.
The way that you are calling sortBy as a comparer function feels a bit off. Maybe it's just a confusing name?
Also you should not need to include the type IReview in your callback if book is typed correctly as {reviews: IReview[]}.
If you include your sortBy function and sortRule variable then I can be of more help. But here's what I came up with.
import React, { useState, useMemo } from "react";
type IReview = {
rating: number;
}
type Book = {
reviews: IReview[]
}
type SortRule = string; // just a placeholder - what is this really?
declare function sortBy(a: IReview, b: IReview, rule: SortRule): number;
const MyComponent = ({book}: {book: Book}) => {
const [sortRule, setSortRule] = useState("");
const reviews = book.reviews;
const sortedReviews = useMemo( () => {
return [...reviews].sort((review1, review2) =>
sortBy(review1, review2, sortRule)
);
}, [sortRule, reviews]);
...
}
Typescript Playground Link

React-native infinite re-renders

I am build a replica of the 2048 game.
I am working on the animations right now and I seem to get infinite re-renders for some reason when updating the state with any kind of array.
It seems that passing anything but an array to 'setAppearAnimations' works fine.
Thanks in advance :)
const GameProvider = (props) => {
const [appearAnimations, setAppearAnimations] = useState([])
const addAppearAnimation = (animation) => {
const newAppearAnimations = appearAnimations.concat([animation])
console.log(newAppearAnimations)
setAppearAnimations(newAppearAnimations)
}
const Piece = ({ piece }) => {
const { value, prevX, prevY, x, y, hasJustAppeared } = piece
let appearAnimation = 1
useEffect(() => {
//Add appear animation
if (hasJustAppeared) {
appearAnimation = new Animated.Value(0)
addAppearAnimation(appearAnimation)
}
}, [])
I changed the way the animations are being called so now i declare it inside A Piece component and start it inside a 'useEffect' inside it.

React Hooks reversing syntax of useState - pretty strange

So I've used React Hooks somewhat heavily in the past few months on one particular project - this is the first time I've seen anything like this and was wondering if anybody had an explanation as to what is happening.
I have the following:
const [ setSectionDefects, sectionDefects ] = useState([]);
const { propertyDefects, propertySections } = props;
useEffect(()=> {
const defectsBySection = [];
propertySections.map(propertySection => {
const sectionId = propertySection.SectionId;
const defectArray = [];
const sectionObject = { id: sectionId, defects: defectArray };
propertyDefects.map(propertyDefect => {
if (propertyDefect.WosectionId == sectionId) {
defectArray.push(propertyDefect);
}
})
defectsBySection.push(sectionObject);
})
// setSectionDefects(defectsBySection);
// sectionDefects(defectsBySection);
}, []);
console.log(setSectionDefects, sectionDefects)
When the code reaches the console.log statement, it says that 'setSectionDefects' is an array and 'sectionDefects' is the function to set it!
My mind is blown, I can't figure it out for the life of me - the syntax, as I've learned it, is the function declaration first, and then the variable to be set -
ie: const [ setSectionDefects, sectionDefects ] = useState([]);
Has anyone else ran into this?
the first item in useState is the state itself and and second item is function to update it.
https://reactjs.org/docs/hooks-reference.html#usestate.
In your case just swipe the names.
const [ sectionDefects , setSectionDefects] = useState([]);
You have array destructuring the wrong way around.
It should be:
const [ sectionDefects, setSectionDefects ] = useState([]);

Resources