0

What I am trying to do?

I want to create a function that can make any type of API request for a frontend application. Basically, I want to get fancy.

Problem?

I am in over my head and I need another eye to take a look at my broken approach.

Here is the error I get from the code below:

Type '<T>(options: TApiHookOptions) => { data: Accessor<T | null>; error: Accessor<Error | null>; loading: Accessor<boolean>; request: void; }' is not assignable to type 'IUseApiHook'.
  Call signature return types '{ data: Accessor<unknown>; error: Accessor<Error | null>; loading: Accessor<boolean>; request: void; }' and 'TApiHook<T>' are incompatible.
    The types of 'data' are incompatible between these types.
      Type 'Accessor<unknown>' is not assignable to type 'T'.
        'T' could be instantiated with an arbitrary type which could be unrelated to 'Accessor<unknown>'.ts(2322)

Code (everything is here):

import { createEffect, createSignal } from "solid-js";
import axios from "axios";

export type TApiResponse<T> = {
  data: T;
  error: Error | null;
};

export type TApiHook<T> = {
  data: T | null;
  error: Error | null;
  loading: boolean;
  request: () => void;
};

export type TApiHookOptions = {
  url: string;
  method?: "get" | "post" | "put" | "delete";
  data?: any;
};

export interface IUseApiHook {
  <T>(options: TApiHookOptions): TApiHook<T>;
}

export const useApiHook: IUseApiHook = <T>(options: TApiHookOptions) => {
  const [data, setData] = createSignal<T | null>(null);
  const [error, setError] = createSignal<Error | null>(null);
  const [loading, setLoading] = createSignal(false);

  const request = createEffect(() => {
    setLoading(true);
    axios({
      url: options.url,
      method: options.method || "get",
      data: options.data,
    })
      .then((response) => {
        setData(response.data);
        setError(null);
      })
      .catch((error) => {
        setError(error);
      })
      .finally(() => {
        setLoading(false);
      });
  });

  return {
    data,
    error,
    loading,
    request,
  };
};
skyboyer
  • 22,209
  • 7
  • 57
  • 64
Radu Gafita
  • 51
  • 2
  • 4

1 Answers1

0

I don't know solid-js but looking at the docs and types the first returned value is a function that returns the stateful value.

That means you want to do this:

  return {
    data: data(),
    error: error(),
    loading: loading(),
    request,
  };

But there is still a problem with request. You have it typed as a function, but createEffect returns void. I don't think you want an effect here at all (an effect is a function run after any render in which the depedencies have changed). This is a callback (something you run when a user takes an action), not an effect. In plain react you would use useCallback() to memoize this function. But I guess solid-js would just memoize it?

  const request = createMemo(() => {
    setLoading(true);
    axios({
      url: options.url,
      method: options.method || "get",
      data: options.data,
    })
      .then((response) => {
        setData(response.data);
        setError(null);
      })
      .catch((error) => {
        setError(error);
      })
      .finally(() => {
        setLoading(false);
      });
  }, options);

Though I'm not 100% sure what solid-js would recommend here, or if the memoization is required (it's required in plain react if this function would be used as the dependencies to any other hook).

Working Playground

Alex Wayne
  • 178,991
  • 47
  • 309
  • 337