skip to Main Content

I want to fetch new data using the new parameter being passed to the useFetch hook which has a default fetch when the app mounts for the first time. The second fetch and more fetches are supposed to happen when the user fills in the input to enter a new city and submits, then that city’s data is brought back.

useFetch Hook

import { useQuery } from "@tanstack/react-query";

const useFetch = function (city:string='kampala') {
  const { isLoading, error, data } = useQuery({
    queryKey: ["weatherData"],
    queryFn: () =>
      fetch(
        `http://api.weatherapi.com/v1/forecast.json?key=516e576e876042b9b7665337232403&q=${city}&days=7`
      ).then((res) => res.json()),
  });

  return { isLoading, error, data };
};

export default useFetch;

The search component below

import React, { useState } from "react";
import { BsSearch } from "react-icons/bs";
import useFetch from "../../../hooks/useFetch";

import "./header.css";

type Props = {};

function Header({}: Props) {
  // const [city, setCity] = useState("kampala");

  const city = React.useRef<HTMLInputElement>(null);

  const handleSubmit = async (e: any) => {
    e.preventDefault();
    useFetch(city.current?.value);
    // city.current.value = "";
    window.scrollTo({
      top: 0,
      behavior: "smooth",
    });
  };

  return (
    <div className="lovez">
      <div className="lovek">
        <h1 style={{ color: "#F5AF26" }}>Weather Forcast</h1>
        <p>{new Date("2023-03-24").toDateString()}</p>
      </div>
      <div className="header__search">
        <form onSubmit={handleSubmit}>
          <input
            type="text"
            className="home__input"
            ref={city}
            name="city"
            placeholder="Choose City"
          />
          <button type="submit" className="weather__button">
            Search
          </button>
        </form>
      </div>
    </div>
  );
}

export default Header;

2

Answers


  1. First, you should save city in state instead of ref, so that when you change it, the component is re-rendered.

    Second, you should pass city to the queryKey paramater, react-query will automatically resend the request when the city changes. You can read more about this here.

    It should look something like the following:

    const useFetch = function (city:string='kampala') {
      const { isLoading, error, data } = useQuery({
        queryKey: ["weatherData", city],
        queryFn: () =>
          fetch(
            `http://api.weatherapi.com/v1/forecast.json?key=516e576e876042b9b7665337232403&q=${city}&days=7`
          ).then((res) => res.json()),
      });
    
      return { isLoading, error, data };
    };
    
    const [city, setCity] = useState("kampala");
    const {data} = useFetch(city);
    
    const handleSubmit = async (e: any) => {
      e.preventDefault();
    
      setCity(e.target.city.value);
    
      window.scrollTo({
        top: 0,
        behavior: "smooth",
      });
    };
    
    Login or Signup to reply.
  2. First of all, you are violating rules of hooks by calling useFetch inside a "standard JavaScript" function handleSubmit that is neither a React function component nor a custom React Hook function. You useFetch becomes a hook as you have written another hook inside it useQuery.

    I would suggest you to rename useFetch to something more relatable like useCityWeather.

    For the actual solution, you can do following as suggested by @Enve: use a local state to store the city data and whenever it’s updated, all the hooks inside the component including useFetch will be called again with the updated state.

    In case you want to initialize the state with a data passed by props, you can write like this:

    const [city, setCity] = useState(props.city || ''); // Optional || ''
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search