React Hooks - создание запроса Ajax

Я только начал играть с хуками React, и мне интересно, как должен выглядеть запрос AJAX?

Я пробовал много попыток, но не могу заставить его работать, а также не знаю, как лучше всего это реализовать. Ниже моя последняя попытка:

import React, { useState, useEffect } from 'react';

const App = () => {
    const URL = 'http://api.com';
    const [data, setData] = useState({});

    useEffect(() => {
        const resp = fetch(URL).then(res => {
          console.info(res)
        });
    });

    return (
        <div>
          // display content here
        </div>
    )
}
"Я пробовал много попыток" Что пробовали? "не могу заставить его работать" Что произошло вместо того, что вы ожидали?
Alex Wayne 30.10.2018 08:14

Отвечает ли это на ваш вопрос? каков правильный способ вызова API в React JS?

sleske 14.05.2021 09:05
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
22
2
24 053
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Ответ принят как подходящий

Вы можете создать собственный перехватчик под названием useFetch, который будет реализовывать перехватчик useEffect.

Передача пустого массива в качестве второго аргумента ловушке useEffect вызовет запрос на componentDidMount.

Вот демонстрация в песочнице кода.

См. Код ниже.

import React, { useState, useEffect } from 'react';

const useFetch = (url) => {
  const [data, setData] = useState(null);

  // empty array as second argument equivalent to componentDidMount
  useEffect(() => {
    async function fetchData() {
      const response = await fetch(url);
      const json = await response.json();
      setData(json);
    }
    fetchData();
  }, [url]);

  return data;
};

const App = () => {
    const URL = 'http://www.example.json';
    const result = useFetch(URL);

    return (
      <div>
        {JSON.stringify(result)}
      </div>
    );
}

Что, если я хочу повторно использовать тот же ответ в другом компоненте? каждый вызов useFetch будет обновлять данные, даже если URL такой же. Невозможно использовать один и тот же useState для двух компонентов.

Walid Ammar 10.12.2018 14:22

@WalidAmmar Я думаю, вы могли бы переместить useFetch в родительский компонент и передать данные оттуда

Paul Fitzgerald 10.02.2019 21:29

Почему бы вместо этого не использовать Context?

Walid Ammar 11.02.2019 13:46

@PaulFitzgerald, откуда взялась ваша собственность data? Вы должны получить ReferenceError: data is not defined.

Daniel 18.03.2021 18:28

@peterflanagan, а как насчет ошибки CORS, как вы этого избежали?

Daniel 19.03.2021 19:31

Работает нормально ... Вот и все:

import React, { useState, useEffect } from 'react';

const useFetch = url => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  const fetchUser = async () => {
    const response = await fetch(url);
    const data = await response.json();
    const [user] = data.results;
    setData(user);
    setLoading(false);
  };

  useEffect(() => {
    fetchUser();
  }, []);

  return { data, loading };
};

const App = () => {
  const { data, loading } = useFetch('https://api.randomuser.me/');

  return (
    <div className = "App">
      {loading ? (
        <div>Loading...</div>
      ) : (
        <React.Fragment>
          <div className = "name">
            {data.name.first} {data.name.last}
          </div>
          <img className = "cropper" src = {data.picture.large} alt = "avatar" />
        </React.Fragment>
      )}
    </div>
  );
};

Живая демонстрация:

Edit x908rkw8yq

Редактировать

Обновлено в зависимости от изменения версии (спасибо @mgol за то, что предоставил его мое внимание в комментариях).

Вы не должны передавать асинхронную функцию для useEffect, поскольку такая функция возвращает обещание вместо функции очистки. В таком случае Newer React выведет на консоль предупреждение: codeandbox.io/s/1qn0pjx0oj

mgol 22.02.2019 12:06

@mgol Кажется, что в остальных ответах все еще есть такая же проблема :)

SakoBu 23.02.2019 21:12

@SakoBu Спасибо за обновление. Я удалил свой голос против и проголосовал за ваше сообщение. Очистки нет, но здесь она может и не понадобиться.

mgol 24.02.2019 01:28

Проголосовали против. Этот ответ может быть связан с некоторым объяснением того, что происходит в коде, поэтому он более полезен для новичков, которые нашли этот вопрос.

David Yell 02.08.2021 14:20

Пока что отличные ответы, но я добавлю специальный крючок, когда вы хотите инициировать запрос, потому что вы тоже можете это сделать.

function useTriggerableEndpoint(fn) {
  const [res, setRes] = useState({ data: null, error: null, loading: null });
  const [req, setReq] = useState();

  useEffect(
    async () => {
      if (!req) return;
      try {
        setRes({ data: null, error: null, loading: true });
        const { data } = await axios(req);
        setRes({ data, error: null, loading: false });
      } catch (error) {
        setRes({ data: null, error, loading: false });
      }
    },
    [req]
  );

  return [res, (...args) => setReq(fn(...args))];
}

Вы можете создать функцию, используя этот крючок для определенного метода API, если хотите, но имейте в виду, что эта абстракция не является строго обязательной и может быть довольно опасной (свободная функция с крючком не является хорошей идеей, если она используется вне контекста функции компонента React).

const todosApi = "https://jsonplaceholder.typicode.com/todos";

function postTodoEndpoint() {
  return useTriggerableEndpoint(data => ({
    url: todosApi,
    method: "POST",
    data
  }));
}

Наконец, из вашего функционального компонента

const [newTodo, postNewTodo] = postTodoEndpoint();

function createTodo(title, body, userId) {
  postNewTodo({
    title,
    body,
    userId
  });
}

А затем просто укажите createTodo на обработчик onSubmit или onClick. newTodo будет иметь ваши данные, статус загрузки и ошибки. Код песочницы Прямо здесь.

То, как вы вызываете запрос ajax внутри useEffect, неверно. Согласно документу, функция useEffect должна быть чистой, так как она также выполняет очистку автоматически. Итак, правильный способ: useEffect(() => { fetchData() }), где fetchData можно записать как: async function fetchData() { ...}.

Ashish Rawat 01.03.2019 13:35

Привет, @AshishRawat, какие документы вы имеете в виду? Можно ссылку, пожалуйста?

Rohman HM 26.10.2019 17:57

Вот кое-что, что, я думаю, сработает:

import React, { useState, useEffect } from 'react';

const App = () => {
    const URL = 'http://api.com';
    const [data, setData] = useState({})

    useEffect(function () {
      const getData = async () => {
        const resp = await fetch(URL);
        const data = await resp.json();

        setData(data);
      }
      getData();
    }, []);

    return (
      <div>
        { data.something ? data.something : 'still loading' }
      </div>
    )
}

Есть несколько важных моментов:

  • Функция, которую вы передаете useEffect, действует как componentDidMount, что означает, что она может выполняться много раз. Вот почему мы добавляем пустой массив в качестве второго аргумента, что означает: «Этот эффект не имеет зависимостей, поэтому запускайте его только один раз».
  • Компонент App все еще что-то отображает, даже если данных еще нет. Таким образом, вы должны обрабатывать случай, когда данные не загружаются, но компонент отображается. Между прочим, здесь нет никаких изменений. Мы делаем это даже сейчас.

Такой подход не подходит. Вы получите следующее предупреждение: index.js:1 Warning: An effect function must not return anything besides a function, which is used for clean-up. It looks like you wrote useEffect(async () => ...) or returned a Promise. Instead, write the async function inside your effect and call it immediately: useEffect(() => { async function fetchData() { // You can await here const response = await MyAPI.getData(someId); // ... } fetchData(); }, [someId]); // Or [] if effect doesn't need props or state

Jason Rice 27.08.2020 18:27

Только что обновил фрагмент. Проблема была в async перед закрытием.

Krasimir 28.08.2020 09:51

Традиционно вы должны написать вызов Ajax в жизненном цикле componentDidMount компонентов класса и использовать setState для отображения возвращенных данных после возврата запроса.

С хуками вы должны использовать useEffect и передать пустой массив в качестве второго аргумента, чтобы выполнить обратный вызов один раз при монтировании компонента.

Вот пример, который извлекает случайный профиль пользователя из API и отображает его имя.

function AjaxExample() {
  const [user, setUser] = React.useState(null);
  React.useEffect(() => {
    fetch('https://randomuser.me/api/')
      .then(results => results.json())
      .then(data => {
        setUser(data.results[0]);
      });
  }, []); // Pass empty array to only run once on mount.
  
  return <div>
    {user ? user.name.first : 'Loading...'}
  </div>;
}

ReactDOM.render(<AjaxExample/>, document.getElementById('app'));
<script src = "https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src = "https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

<div id = "app"></div>

В приведенных выше ответах я обнаружил много неправильного использования useEffect.

В useEffect не следует передавать асинхронную функцию.

Посмотрим подпись useEffect:

useEffect(didUpdate, inputs);

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

Любая асинхронная функция вернет обещание, но не функцию, поэтому функция удаления на самом деле не действует.

Так что передайте асинхронную функцию, которая может справиться с вашими побочными эффектами, но это антипаттерн Hooks API.

Я бы рекомендовал вам использовать реагировать-запрос-ловушка, поскольку он охватывает множество вариантов использования (несколько запросов одновременно, отменяемые запросы при размонтировании и состояния управляемого запроса). Он написан на машинописном тексте, поэтому вы можете воспользоваться этим, если в вашем проекте также используется машинописный текст, а если нет, в зависимости от вашей IDE вы можете увидеть подсказки типа, а библиотека также предоставляет некоторые помощники, которые позволят вам безопасно введите полезные данные, которые вы ожидаете в результате запроса.

Он хорошо протестирован (100% покрытие кода), и вы можете использовать его просто так:

function UserProfile(props) {
  const [user, getUser] = useResource((id) => {
    url: `/user/${id}`,
    method: 'GET'
  })

  useEffect(() => getUser(props.userId), []);

  if (user.isLoading) return <Spinner />;
  return (
    <User 
      name = {user.data.name}
      age = {user.data.age}
      email = {user.data.email}
    >  
  )
}

пример изображения

Отказ от ответственности автора: мы использовали эту реализацию в производственной среде. Есть множество ловушек для работы с обещаниями, но есть и крайние случаи, которые не рассматриваются или не выполняется достаточное количество тестов. response-request-hook прошел боевые испытания еще до официального релиза. Его главная цель - быть хорошо протестированным и безопасным в использовании, поскольку мы имеем дело с одним из наиболее важных аспектов наших приложений.

Отличная библиотека Матеуса! Один вопрос: как вы имитируете аксиомы для тестирования приложения с помощью response-request-hook? Использовать с ним axios-mock-adapter мне не удалось.

Nicolas Bonnefon 13.04.2019 23:05

использовать-http - это небольшой перехватчик useFetch, используемый как: https://use-http.com

import useFetch from 'use-http'

function Todos() {
  const [todos, setTodos] = useState([])
  const { request, response } = useFetch('https://example.com')

  // componentDidMount
  useEffect(() => { initializeTodos() }, [])

  async function initializeTodos() {
    const initialTodos = await request.get('/todos')
    if (response.ok) setTodos(initialTodos)
  }

  async function addTodo() {
    const newTodo = await request.post('/todos', {
      title: 'no way',
    })
    if (response.ok) setTodos([...todos, newTodo])
  }

  return (
    <>
      <button onClick = {addTodo}>Add Todo</button>
      {request.error && 'Error!'}
      {request.loading && 'Loading...'}
      {todos.map(todo => (
        <div key = {todo.id}>{todo.title}</div>
      )}
    </>
  )
}

или, если вы не хотите самостоятельно управлять состоянием, вы можете сделать

function Todos() {
  // the dependency array at the end means `onMount` (GET by default)
  const { loading, error, data } = useFetch('/todos', [])

  return (
    <>
      {error && 'Error!'}
      {loading && 'Loading...'}
      {data && data.map(todo => (
        <div key = {todo.id}>{todo.title}</div>
      )}
    </>
  )
}

Живая демонстрация

Edit Basic Example

Другие вопросы по теме