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>
);
};
Демонстрация в реальном времени:
Ответ 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>
)}
</>
)
}