Реагирует только иногда выбрасывает свойство cannot map ‘map’ неопределенного

#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>