#javascript #reactjs #axios #react-query
#javascript #reactjs #axios #реагировать-запрос
Вопрос:
Я новичок в react и делаю проект. Я создаю страницу с 3 компонентами card. Все они используют react query и Axios для извлечения данных. Моя страница иногда работает, но продолжает случайным образом выдавать мне ошибки о неопределенном свойстве ‘map’. Я использую пустой массив в качестве начального состояния в моем перехватчике useState, но я все еще иногда получаю эту ошибку. Невозможно понять, почему это происходит.
Это код для моего компонента card. У меня есть две другие карты с аналогичным кодом.
import React,{useState} from "react";
import {Card, Spin, Divider, List, Tag} from 'antd';
import axiosInstance from "../axios";
import { useQuery } from 'react-query';
const { Meta } = Card;
const MyPlantMatrix=()=> {
const[myPlantMatrix,setMyPlantMatrix] = useState([]);
const {status, data, error, isFetching} = useQuery('myplantmatrix', async ()=>{
const {data} = await axiosInstance.get("api_url/?format=json");
console.log(data);
setMyPlantMatrix(data);
return data;
})
const renderedPlantMatrix = myPlantMatrix.map(
plants => {
return <Card style = {{ width:400}} key={plants}>
<Meta title="My Assigned Plants"></Meta>
<Divider/>
{plants.assigned_plant_code.map(
plantcode => {
return <Tag>{plantcode}</Tag>
}
) }
</Card>
}
)
return(
<div>
{status === 'loading' ? (
<Spin/>
): status === 'error' ? (
error.message
):(
<>
{renderedPlantMatrix}
<div>{
isFetching ? 'Data is updating in background...' : ''
}</div>
</>
)}
</div>
);
}
export default MyPlantMatrix;
Это код для моей страницы, где я вызываю вышеупомянутый компонент для рендеринга.
import React, {useState,useEffect} from "react";
import { List,Card } from 'antd';
import MyBuyerProfile from "../../components/MyBuyerProfile";
import MyAssignedPlants from "../../components/MyPlantMatrix";
import MyMarketMatrix from "../../components/MyMarketMatrix";
const gridStyle = {
width: '25%',
textAlign: 'center',
};
function MyAccountView() {
return (
<Card title="My Profile">
<List
grid={{
gutter: 16,
xs: 1,
sm: 2,
md: 4,
lg: 4,
xl: 6,
xxl: 3,
}}>
<List.Item>
<MyBuyerProfile/>
</List.Item>
<List.Item>
<MyAssignedPlants/>
</List.Item>
<List.Item>
<MyMarketMatrix/>
</List.Item>
</List>
</Card>
);
}
export default MyAccountView;
Вот код для экземпляра Axios, в который я добавляю заголовки аутентификации. Я продолжаю получать сообщение об ошибке
«Произошла ошибка сервера / сети, похоже, проблема может быть в CORS. Извините за это — мы скоро это исправим.» иногда, когда я обновляю. Я не могу понять, почему это срабатывает.
// import React from "react";
import axios from 'axios';
// import { Alert } from 'antd';
const baseURL = MyBaseURL;
const axiosInstance = axios.create({
baseURL: baseURL,
timeout: 5000,
headers: {
Authorization: localStorage.getItem('access_token')
? 'Bearer ' localStorage.getItem('access_token')
: null,
'Content-Type': 'application/json',
accept: 'application/json',
},
});
axiosInstance.interceptors.response.use(
(response) => {
return response;
},
async function (error) {
const originalRequest = error.config;
if (typeof error.response === 'undefined') {
alert(
'A server/network error occurred. '
'Looks like CORS might be the problem. '
'Sorry about this - we will get it fixed shortly.'
);
return Promise.reject(error);
}
if (
error.response.status === 401 amp;amp;
originalRequest.url === baseURL 'auth/jwt/refresh/'
) {
window.location.href = '/account/Login/';
return Promise.reject(error);
}
if (
error.response.data.code === 'token_not_valid' amp;amp;
error.response.status === 401 amp;amp;
error.response.statusText === 'Unauthorized'
) {
const refreshToken = localStorage.getItem('refresh_token');
if (refreshToken) {
const tokenParts = JSON.parse(atob(refreshToken.split('.')[1]));
// exp date in token is expressed in seconds, while now() returns milliseconds:
const now = Math.ceil(Date.now() / 1000);
console.log(tokenParts.exp);
if (tokenParts.exp > now) {
return axiosInstance
.post('/auth/jwt/refresh/', { refresh: refreshToken })
.then((response) => {
localStorage.setItem('access_token', response.data.access);
localStorage.setItem('refresh_token', response.data.refresh);
axiosInstance.defaults.headers['Authorization'] =
'Bearer ' response.data.access;
originalRequest.headers['Authorization'] =
'Bearer ' response.data.access;
return axiosInstance(originalRequest);
})
.catch((err) => {
console.log(err);
});
} else {
console.log('Refresh token is expired', tokenParts.exp, now);
window.location.href = '/account/Login/';
}
} else {
console.log('Refresh token not available.');
window.location.href = '/account/Login/';
}
}
// specific error handling done elsewhere
return Promise.reject(error);
}
);
export default axiosInstance;
Пожалуйста, помогите мне понять и укажите мне соответствующие ресурсы, чтобы узнать больше об этом.
Комментарии:
1. Вы используете только
map
в двух местах. Поставьте на них условные точки останова, когда вы их вызываетеundefined
. Посмотрите, чтоdata
вы получаете от axios. Иногда это либо не массив, либо иногда массив, в котором по крайней мере одна запись не является объектом или является объектом безassigned_plant_code
свойства (или, по крайней мере, со значениемundefined
).2. Является ли это setMyPlantMatrix (data); всегда задает массив?
Ответ №1:
Я думаю, что существует концептуальное недопонимание в том, как работает react-query: данные, возвращаемые из функции запроса, будут доступны вам в data
свойстве, возвращаемом react-query. Нет необходимости в каком-либо дополнительном локальном состоянии — особенно когда вы пытаетесь установить его во время функции запроса. Эти данные могут быть undefined
, если вы находитесь в состоянии загрузки или ошибки, поэтому вам нужно сначала проверить это. Пожалуйста, также взгляните на все примеры в документации.
Вот как это обычно выглядит, учитывая, что ваша функция запроса вернет массив:
const MyPlantMatrix=()=> {
const {status, data, error, isFetching} = useQuery('myplantmatrix', async ()=>{
const {data} = await axiosInstance.get("api_url/?format=json");
console.log(data);
return data;
})
// here, data will be potentially undefined
if (status === 'loading') return <Spin/>
if (status === 'error') return error.message
// now, data should not be undefined
const renderedPlantMatrix = data.map(...)
return (
<>
{renderedPlantMatrix}
<div>{isFetching ? 'Data is updating in background...' : ''}</div>
</>
)
}
В качестве альтернативы, вы можете сделать if (data)
проверку в начале, а затем отобразить свои данные. Это может быть даже лучше, если вы получите ошибку при повторной выборке, потому что тогда у вас будет data
также ошибка, поэтому вам придется решать в каждом конкретном случае, лучше ли отображать ошибку повторной выборки или (устаревшие) данные, которые есть в кэшеили оба.
Комментарии:
1. Спасибо, я реализовал этот метод, однако теперь я понял, что проблема с предупреждением связана с состоянием гонки. Я отображаю 3 компонента на странице MyAccount. Каждый компонент имеет вызов axiosInstance.get, реализованный с использованием react-query.
2. Если все компоненты монтируются одновременно, react-query дедуплицирует запрос для вас. Если они монтируются последовательно, рассмотрите возможность установки small
staleTime
.
Ответ №2:
Я думаю, что проблема здесь. Сначала вам нужно проверить, что assigned_plant_code
это массив, и он имеет length > 0
. Перед ?
проверкой длины убедитесь, что assigned_plant_code
оно существует
{plants.assigned_plant_code?.length amp;amp; plants.assigned_plant_code.map(
plantcode => {
return <Tag>{plantcode}</Tag>
}
) }
Ответ №3:
myPlantMatrix
должен быть массив для map
работы, до и во время выборки данных myPlantMatrix
не будет иметь тип array. Проверьте, если array, то map
он
Использовать Array.isArray(myPlantMatrix) amp;amp; myPlantMatrix.map(...
Ответ №4:
Я думаю, вам не следует использовать useState hook, потому что react query предоставляет данные по умолчанию и эффективно обрабатывает кэширование, и вы устанавливаете свои данные в setState перед возвратом данных. Не используйте перехват useState, потому что должно быть ясно, что запрос react используется для кэширования, извлечения и обновления данных, поэтому он возвращает ваши обновленные данные, и при отображении в react всегда используйте
{plants?.assigned_plant_code.map(
plantcode => {
return <Tag>{plantcode}</Tag>
}
) }
«?» — необязательный знак в react, который игнорирует assigned_plant_code.map — это undefined
, вы также можете сделать так
plants.length > 0 ?
{plants?.assigned_plant_code.map(
plantcode => {
return <Tag>{plantcode}</Tag>
}
) }
: <div>Error</div>