1 year ago

#385721

test-img

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

Accepted video resources