Пользовательский хук React и Typescript для запроса Api (вызывается дважды и не сопоставляется с типом)

#reactjs #typescript #api

#reactjs #typescript #API

Вопрос:

Я нашел онлайн-пример пользовательского хука службы api с typescript, который, как мне показалось, имеет смысл, поэтому я попытался применить его к своим потребностям. Происходит пара вещей, которые для меня не имеют смысла. Во-первых, когда я выхожу из системы, данные, возвращающиеся из api, регистрируются дважды, чего я не понимаю.

Во-вторых, когда я выхожу из service.payload.results, ‘results’ не определен, но если я регистрирую только service.payload, он регистрирует массив, как и ожидалось (за исключением дважды). Я пытаюсь ввести результаты моего вызова api и применить форму к данным. Я не могу понять, что это такое. Вот мой код. Пожалуйста, посмотрите, можете ли вы заметить что-нибудь, чего я не понимаю или явно не понимаю.

  1. Типы доменов (Visitor.ts):
 export interface Visit {
  id: string;
  visited: string;
}

export interface Visitor {
  id: string;
  name: string;
  visits: Array<Visit>;
} 
  
  1. Типы служб (Service.ts):
 interface ServiceInit {
  status: "init";
}
interface ServiceLoading {
  status: "loading";
}
interface ServiceLoaded<T> {
  status: "loaded";
  payload: T;
}
interface ServiceError {
  status: "error";
  error: Error;
}
export type Service<T> =
  | ServiceInit
  | ServiceLoading
  | ServiceLoaded<T>
  | ServiceError;
  
  1. Пользовательский хук:
 import { useEffect, useState } from "react";
import Axios from "axios";
import { Service } from "../types/Service";
import { Visitor } from "../types/Visitor";

export interface Visitors {
  results: Visitor[];
}

const useVisitorService = () => {
  const [result, setResult] = useState<Service<Visitors>>({
    status: "loading",
  });
  const url: string = "api/visitors";
  const config = {
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
    },
  };

  useEffect(() => {
    const requestData = async function () {
      try {
        const res = await Axios.get(url, config);
        setResult({ status: "loaded", payload: res.data });
      } 
      catch (error) {
        setResult({ status: "error", error });
      }
    };
    requestData();
  }, []);

  return resu<
};

export default useVisitorService;

  
  1. Компонент, использующий перехват (VisitList.tsx) :
 import React from "react";
import useVisitorService from "../services/useVisitorService";

const VisitList: React.FC<{}> = () => {
  const service = useVisitorService();

  if (service.status === "loaded") {
    // this logs the expected array of objects from server (twice)
    console.log(service.payload);
  }

  //this errors because results is undefined
  return (
    <div>
      {service.status === "loading" amp;amp; <div>Loading...</div>}
      {service.status === "loaded" amp;amp;
        service.payload.results.map((visitor) => (
          <div key={visitor.id}>{visitor.name}</div>
        ))}
      {service.status === "error" amp;amp; (
        <div>Error.</div>
      )}
    </div>
  );
};

export default VisitList;
  

Комментарии:

1. Рискуя быть спамом, вам может понравиться react-ketting . У него есть несколько хороших хуков, и он также отменяет дублирование запросов GET, даже если они исходят от несвязанных компонентов react.

2. Я посмотрю на это, чтобы увидеть, имеет ли это значение для меня. Спасибо.

Ответ №1:

Это сложная задача, и я не совсем уверен, что происходит. Можете ли вы настроить изолированную среду, в которой есть полный URL для извлечения?

Потенциальная проблема, которую я вижу, может быть или не быть проблемой, в зависимости от того res , как выглядит возвращаемый объект. Я подозреваю, что вы устанавливаете service.payload вместо service.payload.results , но я могу ошибаться.

Через ваши типы вы сказали, что успешный результат выглядит следующим образом:

 interface ServiceLoaded {
    status: "loaded";
    payload: {
        results: Visitor[];
    }
}
  

где полезная нагрузка должна иметь свойство results , представляющее собой массив Visitor объектов.

Это явно не указано или не проверено в вашем коде, что делает его подверженным ошибкам. Вы устанавливаете payload res.data значение, которое может быть любого типа.

 setResult({ status: "loaded", payload: res.data });
  

res.data Содержит ли свойство results , в котором перечислены посетители? Если он просто возвращает список посетителей, вам нужно присвоить его results свойству самостоятельно, и это будет источником ваших проблем. (Или вам нужно изменить тип / интерфейс посетителей, чтобы это был просто посетитель массива [], а не объект, содержащий массив.)

 setResult({ status: "loaded", payload: results: { res.data } });
  

Не зная, как вы res.data на самом деле выглядите, я могу предложить вам использовать защиту типа typescript для его проверки, поскольку в res.data настоящее время Axios имеет тип any . Таким образом, вы знаете, что payload.results это всегда будет определено, если service.status === "loaded" . Если Axios возвращает неожиданный результат, setResult следует сохранить ошибку.

Ваша защита типа выглядит примерно так. Я просто проверяю, что свойство results существует, но вы могли бы получить более подробную информацию и проверить, что это массив и что каждый элемент является Vistor .

 const isValidPayload = (payload: any): payload is Visitors => {
  return typeof payload === "object" amp;amp; "results" in payload;
}
  

Ваша requestData функция теперь выглядит примерно так, где мы все еще улавливаем ошибки, возвращаемые из Axios, но также улавливаем ошибки из-за неверных ответов.

     const requestData = async function () {
        try {
          const res = await Axios.get(url, config);
          if ( isValidPayload(res.data) ) {
            setResult({ status: "loaded", payload: res.data});
          } else {
            setResult({ status: "error", error: {
              name: "Invalid Format",
              message: "API results must contain an array of Visitor objects",
            }});
          }
        } 
        catch (error) {
          setResult({ status: "error", error });
        }
      };