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.log(res)
        });
    });

    return (
        <div>
          // display content here
        </div>
    )
}

Ответ 1

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

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

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

Смотрите код ниже.

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

const useFetch = (url) => {
  const [data, updateData] = useState(undefined);

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

  return data;
};

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

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

Ответ 2

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

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 будет иметь ваши данные, статусы загрузки и ошибок. Код песочницы прямо здесь.

Ответ 3

Работает просто отлично... А вот и вы:

ОБНОВЛЕНИЕ: обновление на основе изменения версии (спасибо @mgol за то, что вы мое внимание в комментариях...

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

Ответ 4

Я бы порекомендовал вам использовать response-request-hook, поскольку он охватывает множество вариантов использования (одновременный многократный запрос, отменяемые запросы на отключение и состояния управляемого запроса). Он написан на машинописном шрифте, поэтому вы можете воспользоваться этим, если ваш проект также использует машинописный текст, и если это не так, в зависимости от вашей среды 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}
    >  
  )
}

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

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

Ответ 5

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

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

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

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

      setData(data);
    }, []);

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

Есть пара важных моментов:

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

Ответ 6

Традиционно вы должны записывать вызов 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>

Ответ 7

Я нашел много неправильных использований useEffect в ответах выше.

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

Давайте посмотрим на подпись useEffect:

useEffect(didUpdate, inputs);

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

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

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

Ответ 8

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

function Todos() {
  const [todos, setTodos] = useState([])

  const request = useFetch('https://example.com')

  // on mount, initialize the todos
  useEffect(() => {
    initializeTodos()
  }, [])

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

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

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