Home > Enterprise >  React: some elements of a data array are not rendered when they are fetched from an API endpoint
React: some elements of a data array are not rendered when they are fetched from an API endpoint

Time:08-12

I faced a strange behavior when I tried to render a data array fetched from an API endpoint.

For example, it is supposed to render 5 items, but only one item is rendered instead (the data sample is below). The problem was solved when I re-fetched the data again. This problem occurs rarely, and I can't find any clear pattern on how it occurred.

I use react 18.2.0, with axios to fetch data from an API endpoint as follows:

const getSelectedCandidatesAsync = async () => {
  const url = API_ENDPOINT   "/candidates/of-voter";

  try {
    const response = await axios.get(url);

    if (response.status === 200) {
      return response.data;
    } else {
      return [];
    }
  } catch (err) {
    console.log(err);
  }
};

Data sample:

[
    {
        "id": 11,
        "full_name": "candidate 1",
        "voting_date": "2022-08-11T02:44:49.000Z"
    },
    {
        "id": 10,
        "full_name": "candidate 2",
        "voting_date": "2022-08-11T02:44:49.000Z"
    },
    {
        "id": 1,
        "full_name": "candidate 3",
        "voting_date": "2022-08-11T02:44:49.000Z"
    },
    {
        "id": 30,
        "full_name": "candidate 4",
        "voting_date": "2022-08-11T02:44:49.000Z"
    },
    {
        "id": 3,
        "full_name": "candidate 5",
        "voting_date": "2022-08-11T02:44:49.000Z"
    }
]
import { useContext, useState, useEffect } from "react"
import { UserContext } from '../App';
import { useNavigate } from "react-router-dom";
import dataStore from "../dataStore";

export default function HomePage() {
    const [selectedCandidates, setSelectedCandidates] = useState([]);
    const [isLoading, setIsLoading] = useState(false);
    const [showContent, setShowContent] = useState(false);
    const obj = useContext(UserContext);
    const navigate = useNavigate();
    const { getSelectedCandidatesAsync } = dataStore();

    useEffect(() => {
        if (obj.user === null) {
            navigate("login", { replace: true });
        } else {
            setIsLoading(true);
            getSelectedCandidatesAsync()
                .then((data) => {
                    if (data.length > 0) {
                        setSelectedCandidates(data);
                    }
                    else {
                        navigate('vote', { replace: true })
                    }

                    setShowContent(true)
                    setIsLoading(false)
                })
        }
    }, [])

    return (
        <div className="container">
            {!isLoading && showContent &&
                <>
                    <div className="row mt-4 align-items-center justify-content-center">
                        <div className="col-md-4 text-center">
                            <h2>Thank you for voting!</h2>
                        </div>
                    </div>

                    <div className="row mt-4 align-items-center justify-content-center">
                        <div className="col-md-6 alert alert-success" role="alert">
                            <div style={{ fontSize: '1.2rem' }} className="mt-4">You have selected <strong>{selectedCandidates.length}</strong> candidate(s):</div>
                            <ul style={{ fontSize: '1.2rem' }}>
                                {selectedCandidates.map(item => <li key={item.id}>{item.full_name}</li>)}
                            </ul>
                        </div>
                    </div>
                </>
            }

            {isLoading &&
                <div style={{ height: '90vh' }} className="row align-items-center justify-content-center">
                    <div className="col-md-4 text-center">
                        <div className="spinner-border" role="status">
                            <span className="visually-hidden">Loading...</span>
                        </div>
                    </div>
                </div>
            }

        </div>
    )
}

CodePudding user response:

The check for if (response.status === 200) is probably unnecessary and may be the source of your problem.

Axios will throw an exception if the request isn't successful so there's no need to check for that. And if the response is a cached HTTP 304 Not Modified, you shouldn't be omitting the data.

Finally, never catch errors / promise rejections in a consumable async function without keeping the promise rejection chain going. Not doing so means your consumer sees a resolved promise with undefined data.

const getSelectedCandidatesAsync = async () => {
  const { data } = await axios.get(`${API_ENDPOINT}/candidates/of-voter`);

  // parsing dates will be helpful
  return data.map(({ voting_date, ...candidate }) => ({
    ...candidate,
    voting_date: new Date(voting_date),
  }));
};

Here's an example component

const Candidates = () => {
  const navigate = useNavigate();

  const [selectedCandidates, setSelectedCandidates] = useState([]);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    if (obj.user === null) {
      navigate("login", { replace: true });
    } else {
      setIsLoading(true);

      getSelectedCandidatesAsync()
        .then((data) => {
          if (data.length > 0) {
            setSelectedCandidates(data);
          } else {
            navigate("vote", { replace: true });
          }
        })
        .catch(console.error)
        .finally(() => {
          setIsLoading(false);
        });
    }
  }, []);

  if (isLoading) {
    return <p>Loading...</p>;
  }

  return selectedCandidates.map(({ id, full_name, voting_date }) => (
    <div key={id}>
      <p>{full_name}</p>
      <p>{voting_data.toLocaleDateString()}</p>
    </div>
  ));
};
  • Related