TypeError: Cannot read property 'Countries' of undefined. Why an array of objects is not recognized and is not filtered?

Viewed 93

I am getting an object from the API ('https://api.covid19api.com/summary'). This object has a key Countries with an array of objects and this array of objects I need to filter.

    const filteredData = data.Countries.filter(dat => {
        return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
      })

TypeError: Cannot read property 'Countries' of undefined.

Why an array of objects is not recognized and is not filtered? In another file, the map method iterates over the same writing data.Countries without error.

const Home = () => {
    const [data, setData] = useState();
    const [searchfield, setSearchfield] = useState('')
    useEffect(() => {
        const fetch = async () => {
            try{
                const res = await axios.get('https://api.covid19api.com/summary');
                setData(res.data);
            }catch(error){
                console.log(error);
            }
        };
        fetch();
    }, []);
    
    const onSearchChange = (event) => {
        setSearchfield(event.target.value)
      }

      const filteredData = data.Countries.filter(dat => {
        return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
      })   
    
     return (
        <div className="main-container">
            <Header searchChange={onSearchChange}/>
            <div className="wrapper">
                <Card data={data}/>
                {/*<div className="graph">
                    <h1>Global Pandemic Crisis Graph</h1>
                    <img src={COVID.image} alt=""/>
                </div>*/}
                <div className="countries">
                    <Countries data={filteredData}/>
                </div>
            </div>
            {/*<Footer />*/}
        </div>
    )
}
3 Answers

You need to filter your data in a callback of the axios response, or it will be "undefined" because it hasn't finished fetching it.

  let filteredData = useRef(null);
  useEffect(() => {
    if (typeof data !== "undefined")
      filteredData.current = data.Countries.filter((dat) => {
        return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
      });
  }, [data]);

  const fetch = async () => {
    const res = await axios
      .get("https://api.covid19api.com/summary")
      .then((response) => {
        // response.data should not be "undefined" here.
        setData(response.data);
      })
      .catch((error) => {
        // Error fallback stuff
        console.error(error);
      });
  };
  if (!filteredData.current) fetch();

Later in your code you can check whether or not it has been defined,

  return (
    <div className="main-container">
      <div className="wrapper">
        {filteredData.current !== null &&
          filteredData.current.map((CountryMap, i) => 
          <div key={i}>{CountryMap.Country}</div>
          )
        }
      </div>
    </div>
  );

When you are fetching the data from an api, you need to use optional chaining ? when applying any higher order function to an array just incase the data haven't been loaded. for example

const filteredData = data?.Countries.filter(dat => {
        return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
      })    

Issue

The initial data state is undefined, so data.Countries is undefined on the initial render.

const [data, setData] = useState();

Solution

Provide valid initial state and guard against later bad updates (if they happen).

const [data, setData] = useState({ Countries: [] });

...

const filteredData = data?.Countries?.filter(dat => {
  return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
})
Related