Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to solve TypeScript error with axios response in React app

I've got the following React function (using the alpha of React Hooks) that has a TypeScript error (thought it still works). The error is pointing to the line of code where the axios promise completes with .then(a=>{setData(a))... It is complaint about a I think.

TS2345: Argument of type 'void | AxiosResponse' is not assignable to parameter of type 'SetStateAction'.   Type 'void' is not assignable to type 'SetStateAction'.

Here is a link to the code: https://codesandbox.io/s/7zyx4vv6k6

Is there more that should be typed in this file? I'm new to TypeScript and was suprised how little I needed to do to make it only complain about this one thing. In codesandbox it does not show me the tpyescript error that I can see. Is there a way to make it show up there like it does in my webstorm IDE? In codesandbox, the line I want to make type correct is 32 even though it does not show it in codesandbox as an error

import { useState, useEffect } from "react";
import axios from "axios";

const useAxiosFetch = (url: string, timeout: number) => {
  const [data, setData] = useState(null);
  const [error, setError] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let unmounted = false;
    let source = axios.CancelToken.source();
    axios
      .get(url, {
        cancelToken: source.token,
        timeout: timeout,
      })
      .catch(function(e) {
        if (!unmounted) {
          setError(true);
          setErrorMessage(e.message);
          setLoading(false);
          if (axios.isCancel(e)) {
            console.log(`request cancelled:${e.message}`);
          } else {
            console.log("another error happened:" + e.message);
          }
        }
      })
      .then(a => {
        if (!unmounted) {
          setData(a);
          setLoading(false);
        }
      });
    return function() {
      unmounted = true;
      source.cancel("Cancelling in cleanup");
    };
  }, []);

  return { data, loading, error, errorMessage };
};

export default useAxiosFetch;
like image 239
Peter Kellner Avatar asked Dec 20 '18 22:12

Peter Kellner


1 Answers

The reason is in this line:

const [data, setData] = useState(null)

Because there is no type parameter provided explicitly for useState, TypeScript infers the type of data based on the type of initial state. In this case, the initial state is null and TypeScript treats this type as the only possible state.

You know that the state will either be null or something else — but TypeScript doesn't. Let's use type parameters to tell it what's really going on.

const [data, setData] = useState<AxiosResponse | null | void>(null);

This gets rid of the error, but looks strange — why is void there? The reason for that is that you catch before you then — and since catch emits a side effect (returns void, in other words), the type propagated to catch is either void or AxiosResponse. Let's fix that by replacing then and catch.

The final solution:

import axios, { AxiosResponse } from "axios";
import { useEffect, useState } from "react";

const useAxiosFetch = (url: string, timeout: number) => {
  const [data, setData] = useState<AxiosResponse | null>(null);
  const [error, setError] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let unmounted = false;
    const source = axios.CancelToken.source();
    axios
      .get(url, {
        cancelToken: source.token,
        timeout,
      })
      .then(a => {
        if (!unmounted) {
          setData(a);
          setLoading(false);
        }
      })
      .catch(function(e) {
        if (!unmounted) {
          setError(true);
          setErrorMessage(e.message);
          setLoading(false);
          if (axios.isCancel(e)) {
            console.log(`request cancelled:${e.message}`);
          } else {
            console.log("another error happened:" + e.message);
          }
        }
      });

    return function() {
      unmounted = true;
      source.cancel("Cancelling in cleanup");
    };
  }, []);

  return { data, loading, error, errorMessage };
};

export default useAxiosFetch;

like image 65
Karol Majewski Avatar answered Nov 14 '22 02:11

Karol Majewski