Search code examples
node.jsreactjsexpressgoogle-places-apiuse-effect

ReactJS How to Run useEffect After Props Value Changes But Only Run It Once


I am using the Google Places API to run a nearby search followed by a detail search to get the hour data for each of the places returned. When I conduct the nearby search I collect all of the placeIds and pass them down as a prop to the component where I run my detail search. I use the useEffect ReactJs hook to make a fetch call to my nodeJs and Express backend and I send the place Ids over in the body of that fetch call. I loop over all of the IDs and add an API call for each ID to a promise array in the backend and then I send the results back over to the frontend.

This works perfectly for what I need. The problem is I am calling this useEffect on the placeId prop value change by passing [props.placeIds] as a dependency to the useEffect hook. Having a dependency in the useEffect hook makes the API call run continuously while the user has the app running. This, coupled with the fact that there are 15+ Ids in each call, makes the app run thousands of API calls per minute which I imagine would be quite costly in production.

I know that if I pass a blank array to the useEffect hook it will only run once. The problem is when I do this the useEffect API call runs before it gets any of the placeIds which crashes the app.

How can I make it so the useEffect API call waits for props.placeIds to change and then runs only one time?

Here is my JSX fetch call to my backend:

useEffect(() => {
    fetch('/api/place-details-search', {
        method: 'POST',
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({
            placesId: props.placeIds,
            dayNumVal: props.dayNumVal,
            numOfPlaces: props.placeIds.length,
        })
    }).then(response => response.json()).then(data => {
        setPlaceHours(data.placeHourText)
    })
}, [props.placeIds])

and here is my nodeJS and express code that loops over the data and sends it back to the front end:

app.post('/api/place-details-search', (req, res) => {
    const dayNumVal = req.body.dayNumVal
    const numOfPlaces = req.body.numOfPlaces
    const placeIds = req.body.placeId
    let placeHourText = [];
    let placeHourName = [];
    let promises = [];
    let parsedDayNumVal
    if (dayNumVal == 0) {
        parsedDayNumVal = 6
    } else {
        parsedDayNumVal = dayNumVal - 1
    }
    for (var i = 0; i < numOfPlaces; i++) {
        promises.push(
            axios.get('https://maps.googleapis.com/maps/api/place/details/json?place_id=' + placeIds[i] + '&fields=name%2Copening_hours&key=' + process.env.REACT_APP_GOOGLE_MAPS_API_KEY).then(response => {
                if (response.data.result !== undefined) {
                   placeHourText.push(response.data.result.opening_hours.weekday_text[parsedDayNumVal]);
                }
                if (response.data.result !== undefined) {
                    placeHourName.push(response.data.result.name)
                }
            })
        )
    }

    Promise.all(promises).then(() => {
        const placeHourTextTwo = []
        for (var i = 0; i < placeHourText.length; i++) {
            let parsedPlaceHourText = placeHourText[i]
            parsedPlaceHourText = parsedPlaceHourText.split("y").pop()
            placeHourTextTwo.push([placeHourName[i], parsedPlaceHourText])
        }
        res.send({ placeHourText: placeHourTextTwo })
    });

});

Solution

  • Having [props.placeIds] as a dependency to the useEffect() means:

    • Hey Effect, please don't run unless props.placeIds changes,

    So if your setPlaceHours(data.placeHourText) function updates props.placeIds then it is differently an infinite loop case.

    A possible solution would be to pass another prop that is derived from placeIds but it doesn't update in each API call