1 year ago
#385721
perrywinkle
React Context - infinite useEffect loop
I am aware of the multiple similar questions regarding this type of error both on articles and on SO as well but none of them have applied or worked for me up until this point.
So, I am using the React Context API to keep track of a state shared between multiple components:
// imports removed because irrelevant
interface IMarkedPlacesContext {
markedPlaces: Place[]
setMarkedPlaces: React.Dispatch<React.SetStateAction<Place[]>>
markPlaces: (places : Place[]) => Promise<void>
}
interface Props {
children: React.ReactNode
}
const MarkedPlacesContext = createContext<IMarkedPlacesContext>(
{} as IMarkedPlacesContext
)
export function MarkedPlacesContextProvider({ children }: Props) {
const [markedPlaces, setMarkedPlaces] = useState<Place[]>([])
const markPlaces = useCallback( async (places: Place[]) => {
const placesWithRatings : Place[] = await Promise.all(places.map(async (place) => {
const placeRating = await FirestorePlaceRatingService.getPlaceRating(place.placeId)
if (placeRating) {
return({
...place,
rating: placeRating.sumRating/placeRating.reviewCount,
reviewCount: placeRating.reviewCount
})
} else {
return(place)
}
}))
setMarkedPlaces(placesWithRatings)
}, [])
return (
<MarkedPlacesContext.Provider
value={{
markedPlaces,
setMarkedPlaces,
markPlaces,
}}
>
{children}
</MarkedPlacesContext.Provider>
)
}
export function useMarkedPlaces() {
return useContext(MarkedPlacesContext)
}
This component sits on the root level of my app. The function to take note of here is markPlaces
which fetches the corresponding rating (if it exists) from my Firestore backend, otherwise, it just gives back the original place
object with rating
as undefined.
Now the problem occurs when I try to use markPlaces
in a useEffect in one of my components:
const PlaceListScreen = () => {
const { markedPlaces, markPlaces, setMarkedPlaces } = useMarkedPlaces()
const { setPlaceInfo, focusPlace } = usePlace()
const { navigate } =
useNavigation<StackNavigationProp<BottomSheetStackParams>>()
useEffect(() => {
if (focusPlace) navigate('PlaceReview')
}, [focusPlace])
// INFINITE LOOP OCCURS HERE! :(
useEffect(() => {
console.log('marking..')
markPlaces(markedPlaces)
}, [])
const handlePressPlace = (place: Place) => {
setPlaceInfo(place)
navigate('PlaceReview')
}
const renderRating = (placeRating: number | undefined) => (
<Subheading style={{flex: 0.1, paddingLeft: 15}}>
{placeRating ? placeRating.toFixed(1) : '-' }
</Subheading>
)
const renderPlaceItem = (place: Place) => (
<TouchableOpacity onPress={() => handlePressPlace(place)}>
<List.Item
title={place.name}
description={place.formattedAddress}
right={() => renderRating(place.rating)}
/>
</TouchableOpacity>
)
return (
<BottomSheetFlatList
data={markedPlaces}
keyExtractor={(place) => place.placeId}
renderItem={(place) => renderPlaceItem(place.item)}
/>
)
}
export default PlaceListScreen
I do not understand why calling markPlaces
in this manner causes an infinite loop. Is it something to do with the way I pass in markedPlaces
as a param? Or is it something to do with the way I have structured my context?
Any help would be much appreciated :)
UPDATE
I have a hacky work-around of the issue, what I did was to make a local state called places
held on the component PlaceListScreen
which essentially acts as a reflection of the main value store in the MarkedRestaurants
Context. Instead of changing the state held within the context, I change the local state. However, this is an extremely ugly solution which I could see definitely causing problems in the future.
I've been mulling over why having the value stored in context is the cause of the infinite re-renders, but I'm still failing to see why. If someone could help me figure out why this is happening and a more appropriate solution for it that would be awesome.
reactjs
react-native
use-effect
react-context
0 Answers
Your Answer