javascriptreactjstypescriptmantine

How to make multiple calls to API and add on to previous state in React Typescrypt


I am very new to React + Typescript and having issues with setState.

Workflow:

  1. Making a call to the API and pulling all of the locations. (working)
  2. Then building out the locations data to be compatible with Mantine MultiSelect component. (working)
  3. I am loading those locations to a multiselect. (working)
  4. If user selects locations I make a call to the API and return reservations for all selected locations(working)
  5. setState so the reservationsData contains data from all API calls (not working)

Questions and issues:

  1. useEffect is triggering twice, googling this issue it is because I am development mode? That is why I implemented a check to see if that location already exists because it was adding it twice and Mantine MultiSelect does not like duplicate values.
  2. When trying to setReservationData, I am unable to add onto previous data. When I display the reservationData it is only showing for the first option that was clicked.
setReservationData(prevReservationData => ({
    ...prevReservationData,
    data
}))
  1. Any other advice on how to refactor this and how I should break this into further components.

Thanks

import React from 'react';
import { MultiSelect } from '@mantine/core';

export const Filters = () => {
    const [regionData, setRegionData] = React.useState([] as any[])
    const [reservationData, setReservationData] = React.useState([])
    const [selectedLocations, setSelectedLocations] = React.useState<string[]>([]);

    interface Locations {
        group: string;
        items: {value: string, label: string}[];
    }
    
    const locations: Locations[] = [];

    React.useEffect(() => {
        async function getCityData(locations: any) {
            const res = await fetch("https://url.com/locations/list?token=123")
            const data = await res.json()
            
            for(let i = 0; i < data.length; i++){
                var temp = [];
                if(data[i]['Cities']){
                    for(let j = 0; j < data[i]['Cities'].length; j++){
                        temp.push({value: data[i]['Cities'][j]['Id'], label: data[i]['Cities'][j]['Name']})
                    }

                    var found = false;
                    for(let k = 0; k < locations.length; k++){
                        if(data[i]['Name'] === locations[k]['group']){
                            found = true;
                        }
                    }

                    if(!found){
                        locations.push({ group: data[i]['Name'], items: temp})
                    }
                }
            }
            setRegionData(locations)
        }
        getCityData(locations)
    }, [])

    const handleLocationChange = (value: string[]) => {
        setSelectedLocations(value);        
    }

    React.useEffect(() => {
        async function getData(location: string) {
            const res = await fetch(`https://url.com/locations/data?token=123=${location}`)
            const data = await res.json()
            setReservationData(prevReservationData => ({
                ...prevReservationData,
                data
            }))
        }

        if(selectedLocations.length > 0){
            for(let i = 0; i < selectedLocations.length; i++){
                getData(selectedLocations[i]);
            }
        }else{
            setReservationData([]);
        }
        
    }, [selectedLocations])

    return (
        <div>
            <MultiSelect
                label="Locations"
                placeholder="Pick your location(s)"
                value={selectedLocations}
                data={regionData}
                onChange={handleLocationChange}
            />
            { JSON.stringify(reservationData) }
        </div>
    );
}

Data from API

Locations

[
    {
        "Id": "111",
        "Name": "West",
        "Cities": [
            {
                "Id": "111a",
                "Name": "Vancouver"
            },
            {
                "Id": "111b",
                "Name": "Seattle"
            }
        ]
    },
    {
        "Id": "222",
        "Name": "East",
        "Cities": [
            {
                "Id": "222a",
                "Name": "Toronto"
            },
            {
                "Id": "222b",
                "Name": "Montreal"
            }
        ]
    }
]

Reservations

[
    {
        "Id": "111a1",
        "Name": "Building A",
        "StreetAddress": "123 Random St"
    },
    {
        "Id": "111a2",
        "Name": "Building B",
        "StreetAddress": "22 Example Dr"
    }
]

Edit: Thanks to Ben it is working now. I have changed reservationData to type any[]

const [reservationData, setReservationData] = React.useState([] as any[])

and changed:

React.useEffect(() => {
    async function getData(location: string) {
        const res = await fetch(`https://url.com/locations/data?token=123=${location}`)
        const data = await res.json()
        console.log(data)
        setReservationData(prevReservationData => [
            ...prevReservationData,
            data 
        ])
    }

    setReservationData([]);
    if(selectedLocations.length > 0){
        setReservationData([]);
        for(let i = 0; i < selectedLocations.length; i++){
            getData(selectedLocations[i]);
        }
    }
    
}, [selectedLocations])

It appears to be working now. I setState to blank then add onto it again to avoid duplicates.

If there is a way to improve me code please let me know.


Solution

  • Reservations data is an array of Reservation objects. So, your state update should look something like this:

    setReservationData(prevReservationData => [
        ...prevReservationData, // Spread the previous Reservations data
        ...data // Spread the new Reservations data
    ])