Yaswanth
Yaswanth

Reputation: 39

React only sometimes is throwing cannot map property 'map' of undefined

I am new to react and doing a project. I am building a page with 3 card components. All of them use react query and Axios for fetching the data. My page works sometimes but keeps throwing me errors about undefined property 'map' randomly. I am using an empty array as the initial state in my useState hook, but I still get this error sometimes. Unable to figure out why this is happening.

This the code for my card component. I have two other cards with similar code.

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;

This is the code for my page where I am calling the above component to render.

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;

Here is the code for the Axios instance where I am adding Auth headers. I keep getting the Alert error "A server/network error occurred Looks like CORS might be the problem. Sorry about this - we will get it fixed shortly." sometimes when I refresh. I am unable to figure out why is this getting triggered.

// 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 &&
            originalRequest.url === baseURL + 'auth/jwt/refresh/'
        ) {
            window.location.href = '/account/Login/';
            return Promise.reject(error);
        }

        if (
            error.response.data.code === 'token_not_valid' &&
            error.response.status === 401 &&
            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;

Kindly help me understand and point me towards relevant resources for reading more about this.

Upvotes: 0

Views: 707

Answers (4)

Build Though
Build Though

Reputation: 442

I think You shouldn't use useState hook because react query provides default data and handles caching effectively and you setting you data into setState before returning data. Don't use UseState hook because it should be clear that react query is used for caching , fetching and updating data so it returns your updated data and while mapping in react always use

{plants?.assigned_plant_code.map(
                 plantcode => {
                     return <Tag>{plantcode}</Tag>
                 }
             ) }

"?" is an optional mark in react which ignores assigned_plant_code.map is it is undefined you can also do like this

plants.length > 0  ? 
{plants?.assigned_plant_code.map(
                 plantcode => {
                     return <Tag>{plantcode}</Tag>
                 }
             ) }
    : <div>Error</div>

Upvotes: 0

kiranvj
kiranvj

Reputation: 34127

myPlantMatrix should be an array for map to work, Before and during your data fetch myPlantMatrix will not be of type array. Check if array then map it

Use Array.isArray(myPlantMatrix) && myPlantMatrix.map(...

Upvotes: 0

TkDodo
TkDodo

Reputation: 28843

I think there is a conceptual misunderstanding in how react-query works: The data returned from the query-function will be available to you in the data property returned by react-query. There is no need for any further local state - especially not when you try to set it during the query-function. That data can be undefined if you are in loading or error state, so you need to check that first. Please also take a look at all the examples in the documentation.

Here is how that would usually look like, given that your query-function will return an Array:

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>
    </>
  )
}

Alternatively, you could make an if (data) check at the beginning and then render your data. This might even be better if you get an error on a re-fetch, because then you will have data as well as an error, so you'd have to decide on a case-by-case basis if it's better to display the refetch error or the (stale) data that the cache has, or both.

Upvotes: 2

thealpha93
thealpha93

Reputation: 778

I think the problem is here. You need to first check that assigned_plant_code is an array and it has length > 0. The ? will make sure that assigned_plant_code exists before checking the length

{plants.assigned_plant_code?.length && plants.assigned_plant_code.map(
                     plantcode => {
                         return <Tag>{plantcode}</Tag>
                     }
                 ) }

Upvotes: 0

Related Questions