Cannot read property 'map' of null - javascript

I've got a React component that makes a call to a backend API which returns an array of objects.
Essentially, I've got a useFetch.js file that fetches data and is then displayed in a Products.js file
My main issue is that when I simply log the data that is returned, no matter how many times I refresh my browser, the correct data is always logged. However, as soon as I include a map to render the data, it displays only for the first time, and then I get an error
Cannot read property 'map' of null
The breakdown in the simple scenario when I just log the data:
Products.js
import React from "react";
import useFetch from "./useFetch";
import DisplayTable from "./DisplayTable"
export default function Products(props) {
const [data, loading, error] = useFetch("http://localhost:8080/products");
console.log("data", data)
console.log("loading", loading)
console.log("error", error)
return (
<div>
</div>
)
}
useFetch.js
import { useState, useEffect } from "react";
export default function useFetch(url) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [data, setData] = useState(null);
async function asyncFetchAPI() {
setLoading(true)
fetch(url, {method: "GET"})
.then(response => response.json())
.then(data => setData(data))
.then(loading => setLoading(false))
.catch((error) => {
console.log('error: ' + error)
setError(true)
})
}
useEffect(() => {
asyncFetchAPI();
}, []);
return [data, loading, error];
}
The above works fine, and I can see the data in the console perfectly well. However, when I modify Products.js to include the map and display as follows
const displayTable = data.map(item => <DisplayTable key={item.id} item={item} />)
The new result is now:
import React from "react";
import useFetch from "./useFetch";
import DisplayTable from "./DisplayTable"
export default function Products(props) {
const [data, loading, error] = useFetch("http://localhost:8080/products");
console.log("data", data)
console.log("loading", loading)
console.log("error", error)
const displayTable = data.map(item => <DisplayTable key={item.id} item={item} />)
return (
<div>
</div>
)
}
At that point, I get
Cannot read property 'map' of null
For what it's worth, my DisplayTable.js function looks like:
import React from "react"
function DisplayTable(props) {
console.log(props)
return (
<h1>{props.item.categoryId}</h1>
)
}
export default DisplayTable

You can fix it by initializing your data state with empty array in your useFetch.js file
const [data, setData] = useState([]);
or in the other file i.e.
const [data = [], loading, error] = useFetch("http://localhost:8080/products");

The problem is you are requesting data in an asynchronous manner right, so the first time, it is returned as null, then you try to map over null.
e.g. Here your data is null initially
const [data, setData] = useState(null);
console.log sometimes may show you the current state of variable, so don't get deceived by that.
So before mapping you can put a condition something like:
data && data.map....
or use empty array as initial value

Related

React error 'Cannot read properties of undefined (reading 'map')'

So, in my code looks like setData works fine and gives the fetched data to my state. However it doesn't work at fist render. But if I cahange something in the code website will be rendered again and country name is gonna show up on the screen just how I wanted.
But this error keeps popping up:
Uncaught TypeError: Cannot read properties of undefined (reading 'common')
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
function Country() {
const { countryName } = useParams();
const [data, setData] = useState([]);
const [country, setCountry] = useState({});
useEffect(() => {
fetch('https://restcountries.com/v3.1/all')
.then(resp => resp.json())
.then(datas => setData(datas));
}, [])
useEffect(() => {
setCountry(data.find(a => a.name.common.toLowerCase() == countryName.replaceAll("-", " ")));
}, [data])
return (
<>
{
<h1>{country.name.common}</h1>
}
</>
)
}
export default Country
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
It means that country.name is undefined (and not an object with common property).
On initial render country is {} (an empty object).
So, country.name returns undefined.
And undefined does not have common property. Hence, the error.
So, a good rule of thumb before accessing a property would be to check if that property even exists.
<h1>{country?.name?.common}</h1>
Most of the time, you don't want to display tags if there is no data.
So, you would like to do something like this.
Markup after && will be rendered only if expression before it, is truthy value.
{country?.name?.common && (
<h1>{country.name.common}</h1>
)}
As country initial value is an empty object, the name property will be undefined, which causes this error, You just need to set an initial value for name too, as an empty object
const [country, setCountry] = useState({ name: {} });
Full example of code.
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
function Country() {
const { countryName } = useParams();
const [data, setData] = useState([]);
const [country, setCountry] = useState({ name: {} });
useEffect(() => {
fetch('https://restcountries.com/v3.1/all')
.then(resp => resp.json())
.then(datas => setData(datas));
}, [])
useEffect(() => {
setCountry(data.find(a => a.name.common.toLowerCase() == countryName.replaceAll("-", " ")));
}, [data])
return (
<>
{
<h1>{country.name.common}</h1>
}
</>
)
}
export default Country
Or you can optionally chain the name property.
<h1>{country.name?.common}</h1>
I opened a new tab in Chrome and paste your api https://restcountries.com/v3.1/all and from the network tab, the results have some strange objects which do not have "name": {"common"....}
You have to filter these objects out

React - Can console log object, but not specific property of said object

I'm doing an exercise to learn React in which I have set up a page with a list of clickable pokemon names which are linking to the pokemons specific detail page. Below is the code of the details page
import { useState, useEffect } from "react";
import axios from "axios";
import { useParams } from "react-router-dom";
export default function DetailsPage() {
const pokeName = useParams();
console.log(pokeName);
const [pokeList, setPokeList] = useState([]);
useEffect(() => {
const fetchData = async () => {
const response = await axios.get(
"https://pokeapi.co/api/v2/pokemon?limit=151"
);
console.log(response.data);
setPokeList(response.data.results);
};
fetchData();
}, []);
const specificPokemon = pokeList.find((pokemon) => {
return pokemon.name === pokeName.pokemon_name;
});
console.log(specificPokemon);
console.log(specificPokemon.name);
return <div><p>{specificPokemon.name}</p></div>;
}
This code has an error I fail to understand
The console.log(specificPokemon) works fine, but the console.log(specificPokemon.name) gives me the following error
Uncaught TypeError: Cannot read properties of undefined (reading 'name')
The correct code is the following, but I wonder why my method doesn't work
const [pokeList2, setPokeList2] = useState([]);
useEffect(() => {
const fetchData = async () => {
const response = await axios.get(
`https://pokeapi.co/api/v2/pokemon/${pokeName.pokemon_name}`
);
console.log(response.data);
setPokeList(response.data);
};
fetchData();
}, []);
console.log(pokeList);
Thank you
When the code runs first the pokeList is an empty array and it cannot find the property name. You should create a second state and do something like this
const pokeName = useParams();
const [pokeList, setPokeList] = useState([]);
const [specificPokemon, setSpecificPokemon] = useState({});
useEffect(() => {
const fetchData = async () => {
const response = await axios.get(
"https://pokeapi.co/api/v2/pokemon?limit=151"
);
setPokeList(response.data.results);
const selectedPokemon = response.data.results.find((pokemon) => {
return pokemon.name === pokeName.pokemon_name;
});
setSpecificPokemon(selectedPokemon)
};
fetchData();
}, [])
And don't forget to make the specificPokemon property optional like this specificPokemon?.name
When your component is mounted, pokeList is an empty array.
React will run the following block before the useEffect hook has finished running:
const specificPokemon = pokeList.find((pokemon) => {
return pokemon.name === pokeName.pokemon_name;
});
console.log(specificPokemon);
console.log(specificPokemon.name);
As long as your array is empty, specificPokemon will be undefined and calling specificPokemon.name will trigger your error.
Beware with console.log, its behavior is not always synchronous.
You might think specificPokemon is properly defined because console.log won't necessarily show undefined.
To verify this, use console.log(JSON.stringify(specificPokemon));.

`object' is undefined due to async function that fetched data in useEffect hook in reactjs

I am fetching an object from api using axios.get("url"). The object fetched successfully (in Animal state) but there is a component level state (imageState) which requires updation using setState with fetched data. Code:Component:
import React,{useEffect, useState} from 'react'
import axios from 'axios'
const AnimalDetail = ({match}) => {
const [Animal ,setAnimal ] = useState({})
const Id = parseInt(match.params.id)
const [imageState, setImageState] = useState ("");
useEffect(()=>{
const fetchAnimal = async () => {
const {data} = await axios.get(`/api/animals/${Id}`)
setAnimal(data)
}
fetchAnimal()
// setImageState(Animal.image[0]) // need to access first index of image object
},[])
useEffect(()=>{
setImageState(Object.values(Animal.image)[0]) // error cant convert undefined to object
}
return (
<>
<h2>imageState </h2> //undefined
<h2>{typeof(Animal.image)}</h2> //gives object
</>
)
}
export default AnimalDetail
Backend Api :
{"id":2,
"image":["/image.jpg","/image2.jpg"],
"price":60000,
"breed":"",
"isAvailable":true,
"weight":110,
}
How can i fetch the data and update the component level state periodically(after fetching)?
You can try following, maybe this can help you. Removed the second useEffect and updated the image state in the first useEffect.
And also I can see, you have declared const [imageState, setImageState] = useState (""); twice. You can remove the second one.
Also, make sure you handle the API error in useEffect otherwise this may break the application on API failure.
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const AnimalDetail = ({ match }) => {
const [Animal, setAnimal] = useState({});
const Id = parseInt(match.params.id);
const [imageState, setImageState] = useState('');
useEffect(() => {
const fetchAnimal = async () => {
const { data } = await axios.get(`/api/animals/${Id}`);
setAnimal(data);
setImageState(data.image[0]);
};
if (Id) {
fetchAnimal();
}
}, [Id]);
return (
<>
<h2>imageState </h2> //undefined
<h2>{typeof Animal.image}</h2> //gives object
</>
);
};
export default AnimalDetail;
your code has some error in the second useEffect.
you can use this one :
useEffect(() => {
if (Animal) setImageState(Object.values(Animal.image)[0]); // error cant convert undefined to object
}, [Animal]);
this is because the Animal should have value first.
and you are defining imageState two times in your code! the first one is enough.

After fetching the api, I see error "TypeError: Cannot read property 'question' of undefined" in ReactJS

I have a parent component called App. I want to send the data that i took from the api(it includes random questions and answers) to child component.In child componenet(QuestionGrid), when i want to take the first question inside the array that come from api, I face the error. i want to use console.log(items[0].question) to see the first question but it fires error.But when I use console.log(items) it allow me to see them. I also aware of taking the data after they loaded.I used also useEffect. Here is my parent component
import './App.css';
import React, { useState,useEffect} from 'react';
import QuestionGrid from './components/QuestionGrid';
function App() {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
useEffect(() => {
fetch("https://opentdb.com/api.php?amount=40&category=9&difficulty=medium&type=multiple")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setItems(result.results);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, [])
return (
<div className="App">
<QuestionGrid isLoaded={isLoaded} items={items}/>
</div>
);
}
export default App;
Here is my child component
import React, { useState, useEffect } from 'react';
export default function QuestionGrid({ isLoaded, items }) {
if(isLoaded){
console.log(items[0].question)
}
return isLoaded ?
<section className="cards">
</section> : <h1>Loading</h1>;
}
It will fire and error because the initial state of items is an empty array. And there is no indexes and object on the items state on the first render.
you can check if the the items is loaded by only checking its length.
return items.length > 0 ? <h1>your jsx component</h1> : <span>Loading...</span>
First thing, you should use the .catch() in fetch like:
fetch("https://opentdb.com/api.php?amount=40&category=9&difficulty=medium&type=multiple")
.then(res => res.json())
.then((result) => {
setIsLoaded(true);
setItems(result.results);
})
.catch(error => {
setIsLoaded(true);
setError(error);
)}
)
You are checking for isLoaded but not if there is any data. You are setting isLoaded(true) in both your result and also in error (which is not bad).
The error is caused because there is nothing in items[0]. To check for this you can call console.log(items?.[0].question) or you can make the check in your if-condition if(items.length > 0)

How to update a function with useEffect (like componentDidUpdate) and use .map function to display from JSON file ReactJS

I've been trying to update a function once data has been recieved and set to a state (using useState). After that the function will use the .map function display the data into a template.
However i am getting two errors, one is 'projects.map is not a function' (btw projects is my state name, where data is stored) and inside the useEffect function which updates when projects is changed 'Expected an assignment or function call and instead saw an expression'
import React, { useState, useEffect } from 'react';
import ProjectSummary from './projectSummary';
function ProjectList() {
// setting my state
const [projects, setProjects] = useState([])
// getting the data from some dummy online data when the app starts
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(data => setProjects({ data }))
}, []);
// makeing a function call postList, which stores a ternery operator
const postList = () => {
// The ternery operator asks if there is anything inside the porjects state
projects.length ? (
// If there is something in the state, it will map out the JSON array in the 'projectSummary template
projects.map(projects => {
return(
<div >
<ProjectSummary key={projects.id} title={projects.title} author={projects.userId} date='30 september, 2019' content={projects.body}/>
</div>
)
})
) : (
// If there isnt anything in the state is prints out 'Loading Data'
<h1>Loading Data</h1>
);
}
// useEffect updates when the 'projects' stae is updated (like componentDidUpdate, and runs the function again
useEffect(() => {
postList()
}, [projects]);
return(
<div className="ProjectList">
// The component should output the postList function, which should map out the array, in the template
{ postList }
</div>
)
}
export default ProjectList
You need to make some corrections to your component
import React, { useState, useEffect } from 'react';
import ProjectSummary from './projectSummary';
function ProjectList() {
const [projects, setProjects] = useState([])
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(data => setProjects(data))
}, []);
return(
<div className="ProjectList">
{
projects.length ?
projects.map(projects => (
<div>
<ProjectSummary key={projects.id} title={projects.title} author={projects.userId} date='30 september, 2019' content={projects.body} />
</div>
))
:
<h1>Loading Data</h1>
}
</div>
)
}
export default ProjectList;
You don't need the postList function and the second useEffect.
You may want to add additional checks to determine when the posts are loading and when they're empty after loading is done, so you don't just get a loading message
try this
const [projects, setProjects] = useState([])
and
.then(data => setProjects(data))
assuming that the data is an array
First you have to set the initial projects to an empty array like this:
const [projects, setProjects] = useState([])
, because currently projects is an empty string, which does not have the map function.
For the fetch, you should write it like this:
useEffect(async () => {
const data = await fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
setProjects(data);
}, []);
Hope this helps.
Array.prototype.map() is for Array.
const [projects, setProjects] = useState('');
projects not an array.

Categories

Resources