Dynamic routing for fetching an API with event handler

123 Views Asked by At

I am trying to create a weather app https://havadurumu-7pqg.vercel.app/ and figure out how to create a dynamic routes for input values of the search form with onSubmit or onClick.

│  pages
│   ├── _app.js
│   ├── _document.js
│   ├── hava
│   │   └── [city].jsx
│   └── index.js
│  

index.js file (shortened):

export default function Home() {

  const [city, setCity] = useState('')
  const [weather, setWeather] = useState([]);
  const [loading, setLoading] = useState(false);
  const router = useRouter();

  const fetchWeather = async (e) => {
    e.preventDefault();
    const axiosRequest = require('axios');
    try {
      const url = `https://api.openweathermap.org/geo/1.0/direct?q=${city}&&appid=${process.env.NEXT_PUBLIC_WEATHER_KEY}`;
      const response = await axiosRequest.get(url);
      const url2 = `https://api.openweathermap.org/data/2.5/forecast/daily?lat=${response.data[0].lat}&lon=${response.data[0].lon}&units=metric&cnt=16&appid=${process.env.NEXT_PUBLIC_WEATHER_KEY}`;
      const response2 = await axiosRequest.get(url2);
      router.push(`/hava/${city}`);
      setWeather(response2);
      // useEffect(() => {
      //   console.log(weather.data)
      // },[weather]);


    } catch (error) {
      console.error(error)
    }

  }


  if (loading) {
    return <Spinner/>
  } else {

    return (

        <form onSubmit={fetchWeather}
                    className={'flex justify-between w-full items-center m-auto p-1 bg-transparent border-2 border-gray-400 text-white rounded-2xl'}>
                <div>
                  <input
                      onChange={(e) => setCity(e.target.value)}
                      className={'bg-transparent border-none text-gray-600 focus:outline-none text-sm'}
                      type="text"
                      placeholder="Search city"/>
                </div>
                <button type="submit">
                  <BsSearch size={20}/>
                </button>
              </form>
            </div>
            {/*Weather*/}
            <div className='lg:mx-auto md:mx-auto  overflow-x-auto justify-around container'>
              {weather.data && <Weather data={weather}/>}
            </div>

              
    );
  }

[city].jsx:

const Weather = ({data}) => {
    // const router = useRouter();
    // const {city} = router.query;
    // console.log(data)

    let dates =[];
    let tempday = [];
    let tempnight = [];
    let conditions = [];
    let description = [];
    let aciklama = []

    try {
         if (data && data.data && data.data.list){
            for (let i = 0; i < 16; i++) {
                const dt = data.data.list[i].dt;
                const options = {month: 'short', weekday: 'short', day: 'numeric'};
                const date = new Date(dt * 1000).toLocaleDateString('tr-TR', options);
                dates.push(date);
                console.log(data.data.list[i].temp.max)
            }



                for (let i = 0; i < 16; i++) {
                    const temp = Math.trunc(data.data.list[i].temp.max);
                    tempday.push(temp);
                }


                for (let i = 0; i < 16; i++) {
                    const temp = Math.trunc(data.data.list[i].temp.min);
                    tempnight.push(temp);
                }

                for (let i = 0; i < 16; i++) {
                    const condition = data.data.list[i].weather[0].main;
                    conditions.push(condition);
                }


                const city = data.data.city.name;


            for (let i = 0; i < 16; i++) {
                const desc = data.data.list[i].weather[0].description;
                description.push(desc)
            }

            const aciklama = description.map((desc) => {
                switch (desc) {
                    case "overcast clouds":
                        return "cok bulutlu";
                    case "broken clouds":
                        return "cok bulutlu";
                    case "scattered clouds":
                        return "az bulutlu";
                    case "few clouds":
                        return "az bulutlu";
                    case "heavy intensity rain":
                        return "çok yağmur";
                    case "moderate rain":
                        return "yağmurlu";
                    case "light rain":
                        return "az yağmur";
                    case "sky is clear":
                        return "güneşli";
                    default:
                        return desc;
                }
            });
            

    } catch (error) {
        console.error(error)
    }


    return ( 
        <div className="flex">
                            <div className='w-[95px]'>
                                <a className="text-gray-50 text-xs font-bold hover:underline">{dates[0]}</a>
                            </div>
                            <div className="max-w-[24px]">
                                {description[0] === "sky is clear" &&
                                    <Image src={sun} alt='sun-icon'/> || description[0] === "light rain" &&
                                    <Image src={lightrain}
                                           alt='rain-cloud-sun-icon'/> || description[0] === "moderate rain" &&
                                    <Image src={moderaterain}
                                           alt='rain-cloud-icon'/> || description[0] === "heavy intensity rain" &&
                                    <Image src={heavyrain}
                                           alt='rain-cloud-icon'/> || description[0] === "overcast clouds" &&
                                    <Image src={cloud} alt="cloud-sun"/> || description[0] === "broken clouds" &&
                                    <Image src={midcloud} alt="cloud-sun"/> || description[0] === "scattered clouds" &&
                                    <Image src={fewcloud} alt="sun"/> || description[0] === "few clouds" &&
                                    <Image src={fewcloud} alt="sun-cloud-icon"/> || description[0] === "Snow" &&
                                    <Image className="w-3/4" src={snow} alt="snow-icon"/>}
                            </div>
                            <div className='pl-2'>
                                    <span
                                        className="p-0.5 text-xs font-normal uppercase tracking-wider text-gray-800 bg-gray-200 rounded-lg bg-opacity-50">{aciklama[0]}
                                    </span>
                            </div>
                        </div>
)}


export default Weather

versions

├── [email protected]
├── [email protected]
├── [email protected]

console.log(response2)

"data": {
    "city": {
        "id": 750269,
        "name": "Bursa",
        "coord": {
            "lon": 29.0675,
            "lat": 40.1827
        },
    },
    "list": [ 
        {// the first day of the 16 days of weather forecast
            "dt": 1699174800,
            "sunrise": 1699159023,
            "sunset": 1699196252,
            "temp": {
                "day": 23.31,
                "min": 16.18,
                "max": 24.06,
                "night": 18.54,
                "eve": 22,
                "morn": 16.39
            },
            
            "pressure": 1010,
            "humidity": 44,
            "weather": [
                        {
                "id": 501,
                "main": "Rain",
                "description": "moderate rain",
                "icon": "10d"
            }
            ],

when submitted the form by searching city on landing page got an

"TypeError: Cannot read properties of undefined (reading 'data') at Weather ([city].jsx:30:33)"

mentioned error line "[city].jsx:30:33" is below:

const dt = data.data.list[i].dt;

possible causes that chatgpt suggests:

• data is being fetched asynchronously and there is a delay in retrieving the data

• make sure that the Weather component is correctly defined to accept the weather prop

• confirm that the Weather component is receiving the weather state as a prop

it seems my weather.jsx file is having troubles to accessing data object that coming from the index.js file.

2

There are 2 best solutions below

0
On BEST ANSWER

SOLVED

I just moved the fetchWeather function from index.js to [city].jsx file. And fetch the weather inside the [city].jsx file with useState hook like so:

for (let i = 0; i < 16; i++) {
                const temp = Math.trunc(weather.list[i].temp.max);
                tempday.push(temp);
            }
2
On

I think your if statement is not correct

if (!data || !data.data || !data.data.list){}

this is saying

if `data` does not exist or 
    data.data does not exist or 
    data.data.list does not exist

execute the code inside if block. maybe at that momemnt data.data has not been created yet. if you change if to:

if (data && data.data && data.data.list){}

your code should work