typescript failed to infer destructuring array type in custom hook

Viewed 1083
import * as React from "react";
import axios from "axios";
import {Fragment, useState, useEffect} from "react";
interface Ihits {
  objectID: string;
  url: string;
  title: string;
}

interface IinitialData<T> {
  hits: Array<T>
}

const useDataApi = (initialUrl:string, initialData: IinitialData<Ihits>) => {
  const [data, setData] = useState(initialData);
  const [url, setUrl] = useState(initialUrl);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);
  useEffect(() => {
    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);
      try {
        const result = await axios(url);
        setData(result.data);
      } catch (error) {
        setIsError(true);
      }
      setIsLoading(false);
    };
    fetchData();
  }, [url]);
  // return [{ data, isLoading, isError }, setUrl];
  return [{data, isLoading, isError }, setUrl];
};

const UseDataFetch: React.FC = () => {
  const [query, setQuery] = useState('redux');
  **const [{data, isLoading, isError}, setUrl] = useDataApi(**
    'https://hn.algolia.com/api/v1/search?query=redux',
    { hits: [] },
  );
  return (
    <Fragment>
      <form
        onSubmit={event => {
          **setUrl(**
            `http://hn.algolia.com/api/v1/search?query=${query}`,
          );
          event.preventDefault();
        }}
      >
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>
      {isError && <div>Something went wrong ...</div>}
      {isLoading ? (
        <div>Loading ...</div>
      ) : (
        <ul>
          {data.hits.map((item: Ihits) => (
            <li key={item.objectID}>
              <a href={item.url}>{item.title}</a>
            </li>
          ))}
        </ul>
      )}
    </Fragment>
  );
}
export default UseDataFetch;

I studied this example which linked https://www.robinwieruch.de/react-hooks-fetch-data

i tried various ways of solution but couldn't set typed destructuring value at all. except using any type and object return like (eg: return {data, isLoading, isError , setUrl};)

it will be thankful someone could tell what i missed

3 Answers

Typescript fails to infer it because you are returning array where each element is of different type. From Typescript's point of view, there is no way it can relate them without you explicitly typing it.

return [{data, isLoading, isError}, setUrl];

First approach would be that you explicitly define return type of useDataApi method:

const useDataApi = (initialUrl:string, initialData: IinitialData<Ihits>): 
[{data: IinitialData; isLoading: boolean; isError: boolean}, string] => {
// your code here
}

Other approach would be that you return object of specific type instead of array. You can define custom type:

type DataApiResponse = {
  response: {
    data: IinitialData<Ihits>;
    isLoading: boolean;
    isError: boolean;
  };
  setUrl: Dispatch<SetStateAction<string>>;
}

And then utilize it as:

return {response: {data, isLoading, isError}, setUrl} as DataApiResponse;

Finally make changes in UseDataFetch method:

const {response, setUrl} = useDataApi(
  "https://hn.algolia.com/api/v1/search?query=redux",
  { hits: [] }
);

const {data, isError, isLoading} = response;

Or just add as const to the return parameter in your custom hook.

return [whatever, whatEver] as const

If TypeScript fails to infer it, then you can just explicitly declare the return type of the function yourself:

// Note return type added after ':'
const useDataApi = (initialUrl:string, initialData: IinitialData<Ihits>): [{data: IinitialData; isLoading: boolean; isError: boolean}, string] => {
  const [data, setData] = useState(initialData);
  const [url, setUrl] = useState(initialUrl);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);
  useEffect(() => {
    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);
      try {
        const result = await axios(url);
        setData(result.data);
      } catch (error) {
        setIsError(true);
      }
      setIsLoading(false);
    };
    fetchData();
  }, [url]);
  // return [{ data, isLoading, isError }, setUrl];
  return [{data, isLoading, isError }, setUrl];
};
Related