Home > Blockchain >  set types to useFetch using generics with typescript
set types to useFetch using generics with typescript

Time:07-04

I have a useFetch, to whom I want to send an argument URL of type string, forgot to mention it is to consume an API. The problem I have is that it does not recognize the result. I am using generic types in useFetch. If I use the types created in my interface everything is clean and there are no errors.

But my idea is that useFetch should be agnostic. The error I get from the linter is Property results does not exist on type <useFetchState<movieApi | null>>. In the useFetch file, I have another error. The argument of type null is not assignable to parameter <useFetchState<movieApi | null>>.

useFetch code.

import { useEffect, useState } from "react"

type useFetchState<T> = {
    data: null | T;
}

export const useFetch = <T>( url: string )  => {
    const [data, setData] = useState<useFetchState<T | null>>(null)
    const [loading, setLoading] = useState<boolean>(false)
    const [error, setError] = useState<string | null>(null)

    const getData = (url:string) => {
        setLoading(true)
        return fetch(url)
            .then((res) => {
                const showError = {
                    err:true,
                    statuscode:res.status,
                    message:"Error in your request"
                }
                if(!res.ok){
                    throw showError;
                }
                return res.json()
            })
            .then((data) => {
                setData(data)
            })
            .catch((err) => setError(err))
            .finally(() => {
                setLoading(false);
            })
    }

    useEffect(() => {
        getData(url)
    }, [url])
    
    return {data, loading, error}
}

Home component code.

import { movieApi, movieApiResult } from "../../hooks/useFetch/interface";
import { useFetch } from "../../hooks/useFetch/useFetch";
import "./home.css";

const Home = () => {
  const IMGPATH = "https://image.tmdb.org/t/p/w1280";

  const { data } = useFetch<movieApi>(
    "https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=04c35731a5ee918f014970082a0088b1&page=2"
  );

  return (
    <div className="container">
      {data?.results.map((item: movieApiResult) => (
        <div
          key={item.id}
          className="item"
          //onClick={() => handleClick(item.id)}
        >
          <img
            src={`${IMGPATH}${item.poster_path}`}
            alt={item.title}
            className="image"
          />
          <p>{item.title}</p>
          <span>{item.vote_average}</span>
        </div>
      ))}
    </div>
  );
};

export default Home;

interfaces

export interface movieApi {
  page: number;
  results: movieApiResult[];
  total_pages: number;
  total_results: number;
}

export interface movieApiResult {
  adult: boolean;
  backdrop_path: string;
  genre_ids: number[];
  id: number;
  original_language: string;
  original_title: string;
  overview: string;
  popularity: number;
  poster_path: string;
  release_date: string;
  title: string;
  video: boolean;
  vote_average: number;
  vote_count: number;
}

ok,removing the useFetchState works, the problem is fixed, but if I create a useContext, and send that data to the Home component, the error appears again in the "results".

type props = {
  children: JSX.Element | JSX.Element[];
};
interface MoviesTypes {
  data: movieApi | movieObject | null;
  loading: boolean;
  setUrl: Dispatch<SetStateAction<string>>;
  handleClick: (id: number) => void;
  IMGPATH: string;
}
const MoviesContext = createContext<MoviesTypes>({} as MoviesTypes);

export const MoviesProvider = ({ children }: props) => {
  const [url, setUrl] = useState<string>(
    "https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=04c35731a5ee918f014970082a0088b1&page=2"
  );
  const [ids, setIds] = useState<null | number>(null);
  const { data, loading } = useFetch<movieApi | movieObject>(url);

  const IMGPATH = "https://image.tmdb.org/t/p/w1280";
  const handleClick = (id: number) => {
    setUrl(
      `https://api.themoviedb.org/3/movie/${id}?api_key=04c35731a5ee918f014970082a0088b1`
    );
    setIds(id);
  };

  return (
    <MoviesContext.Provider
      value={{
        data,
        loading,
        setUrl,
        handleClick,
        IMGPATH,
      }}
    >
      {children}
    </MoviesContext.Provider>
  );
};

export const useMovies = () => {
  const context = useContext(MoviesContext);

  return context;
};

I guess it must be a fairly easy problem. But I'm just getting started with typescript. Thank you very much, best regards.

CodePudding user response:

Your use of useFetchState is slightly off - its type is

{
    data: null | T;
}

but then you pass in a value that must match it (through useState) into it not an object with a possibly null data, but null itself.

useState<useFetchState<T | null>>(null)

The useFetchState isn't doing much for you anyway. I'd change the useState type to be just null or T, no need for anything else.

const [data, setData] = useState<T | null>(null)

CodePudding user response:

In this case, you do not need this:

type useFetchState<T> = {
    data: null | T;
}

Just change your useFetch like below (don't forget the <T,> if you are in .tsx) :

export const useFetch = <T,>(url: string) => {
  const [data, setData] = useState<T>();
  const [loading, setLoading] = useState<boolean>(false);
  const [error, setError] = useState<string | null>(null);

  ...
}

and everything will be OK.

  • Related