Functional component with hooks results in "cannot read property map of undefined" - javascript

I made a classical component and a functional component, they should both do the same thing.
They both pull data from my API and then should map it to a Div. However, this doesn't work with the functional component and I'd rather use a functional component with hooks.
I've also tried using the "UseLayoutEffect" hook. I know this is happening because the first time the component loads, Games is undefined and it tries to map undefined, but after a tiny delay the API call is finished and Games is now an array of objects. However, it already tried to map undefined. I have a condition 'Games' which should stop it from being mapped if its undefined, but for some reason it passes this condition.
Classical component (working):
class Player extends React.Component {
constructor(props) {
super(props);
this.state = {
games: [],
players: {},
};
}
componentDidMount() {
this.fetchData()
}
async fetchData() {
const id = window.location.pathname.split('/')[2];
const games = await axios(`/api/players/${id}`);
this.setState({ games: games.data });
}
render() {
return(
<div>
{this.state.games.map((game, i) => (
<div className="historyId" key={i}>{game.match_id}</div>
))}
</div>
);
}
}
Functional component (not-working):
Uncaught TypeError: Cannot read property 'map' of undefined
at Player (bundle.js:1422)
const Player = (props) => {
let { id } = useParams();
const [games, setGames] = useState({});
useEffect(() => {
async function fetchData() {
const response = await axios(`/api/players/${id}`);
setGames(response);
}
fetchData();
}, []);
return (
<div className="historyContainer">
<h1>Match history here...</h1>
{games && games.data.map((game, i) => <div>{game.match_id}</div>)}
</div>
);
}

You're checking if games exists but it has a default value (empty object) so it will always exist. You're not checking if games.data exists - it won't until your HTTP request is completed.
Try this instead:
{games.data && games.data.map((game, i) => <div>{game.match_id}</div>)}

Try this:
const Player = (props) => {
let { id } = useParams();
const [games, setGames] = useState([]);
useEffect(() => {
async function fetchData() {
const {data} = await axios(`/api/players/${id}`);
setGames(data);
}
fetchData();
}, []);
return (
<div className="historyContainer">
<h1>Match history here...</h1>
{games.map((game, i) => <div>{game.match_id}</div>)}
</div>
);
}

this works for me:
const {products}=useContext(ProductContext);
const [product, setProduct]=useState();
const getProduct=()=>{
if(props.match.params.id){
const res=products;
const data= res.filter(p=>{
return p.id === props.match.params.id;
})
setProduct(data);
}
}
useEffect(() => {
getProduct()
},[])
return (
<div>
{product && product.map(items=>(
<div key={items.id}>
<h2>{items.name}</h2>
</div>
))}
</div>
)
}

Related

why isn't my array being rendered on my page?

I am trying to render listed property information from an array of objects. I used this method in another part of my project with success, but in this instance, I am not getting anything at all.
here is the code I have
import { database } from "../../components/firebase";
import { ref, child, get } from "firebase/database";
import { useState, useEffect } from "react";
export default function Dashboard() {
const dbRef = ref(database);
const [users, setUsers] = useState([]);
const array = [];
const getData = () => {
get(child(dbRef, "users/"))
.then((snapshot) => {
const data = snapshot.val();
setUsers(data);
})
.catch((err) => {
console.log(err);
});
};
const getProperties = () => {
Object.values(users).forEach((user) => {
Object.values(user?.properties).forEach((property) => {
array.push(property);
console.log(property);
});
});
console.log(array);
};
useEffect(() => {
getData();
getProperties();
}, [dbRef]);
return (
<>
<div>Properties </div>
<div>
{array.map((property) => (
<div key={property.property_id}>
<h1>{property?.property_name}</h1>
<p>{property?.description}</p>
<p>{property?.rooms}</p>
<p>{property?.phone}</p>
</div>
))}
</div>
<p>oi</p>
</>
);
}
Nothing happens, it only prints "properties" and "oi"
getData is asynchronous. When you execute getProperties, your users state will still be its initial, empty array value.
You don't appear to be using users for anything else but assuming you want to keep it, the easiest way to drive some piece of state (array) from another (users) is to use a memo hook.
// this is all better defined outside your component
const usersRef = ref(database, "users");
const getUsers = async () => (await get(usersRef)).val();
export default function Dashboard() {
const [users, setUsers] = useState({}); // initialise with the correct type
// Compute all `properties` based on `users`
const allProperties = useMemo(
() =>
Object.values(users).flatMap(({ properties }) =>
Object.values(properties)
),
[users]
);
// Load user data on component mount
useEffect(() => {
getUsers().then(setUsers);
}, []);
return (
<>
<div>Properties </div>
<div>
{allProperties.map((property) => (
<div key={property.property_id}>
<h1>{property.property_name}</h1>
<p>{property.description}</p>
<p>{property.rooms}</p>
<p>{property.phone}</p>
</div>
))}
</div>
<p>oi</p>
</>
);
}
The memo hook will recompute allProperties any time users is changed.
If you don't need the users state, then there's not much need for the memo hook. Instead, just maintain the state you do need
const [allProperties, setAllProperties] = useState([]); // init with empty array
useEffect(() => {
getUsers().then((users) => {
setAllProperties(
Object.values(users).flatMap(({ properties }) =>
Object.values(properties)
)
);
});
}, []);

useEffect is not working in parent Component if child component is added in react

I want to pass data to child component after getting data from API, if I remove the child component its working
const parentComponent= (props) => {
const [loans, setLoans] = useState({});
useEffect(() => {
axios.get(`api`).then((result) =>{
let { data } = result;
console.log('result', result)
setLoan(data);
});
}, []);
return(
<ChildComponent data={loans} />
)
}
You are not setting the to the state loans. You have to use setLoans instead of setLoan.
Check this, it should work now.
const parentComponent= (props) => {
const [loans, setLoans] = useState({});
useEffect(() => {
axios.get(`api`).then((result) =>{
let { data } = result;
console.log('result', result)
setLoans(data);
});
}, []);
return(
<ChildComponent data={loans} />
)
}
const [loans, setLoans] = useState({}); use setLoan or changed name after console.log. You have done setLoans which is not equal to setLoan

Make React JS await for a async func to complete before running

I'm trying to make react not load until after an axios get requests finishes. I'm pretty rough on react all around, so sorry in advance.
I'm getting an array of objects
const { dogBreedsTest } = useApplicationData()
And I need it to be the default value of one of my states
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest);
However, I'm getting an error that my value is coming up as null on the first iteration of my app starting. How can I ensure that my value has completed my request before my app tries to use it?
Here is how I am getting the data for useApplicationData()
const [dogBreedsTest, setDogBreeds] = useState(null);
const getDogBreeds = async () => {
try{
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
}
useEffect(() => {
getDogBreeds()
}, []);
return {
dogBreedsTest,
setDogBreeds
}
And I am importing into my app and using:
import useApplicationData from "./hooks/useApplicationData";
const { dogBreedsTest } = useApplicationData()
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest[0]);
const [breedList1, updateBreedList1] = useState(dogBreedsTest[0])
function handleOnDragEnd(result) {
if (!result.destination) return;
const items = Array.from(dogBreeds);
const [reorderedItem] = items.splice(result.source.index, 1);
items.splice(result.destination.index, 0, reorderedItem);
for (const [index, item] of items.entries()) {
item['rank'] = index + 1
}
updateDogBreeds(dogBreedsTest[0]);
updateBreedList1(dogBreedsTest[0])
}
return (
<div className="flex-container">
<div className="App-header">
<h1>Dog Breeds 1</h1>
<DragDropContext onDragEnd={handleOnDragEnd}>
<Droppable droppableId="characters">
{(provided) => (
<ul className="dogBreeds" {...provided.droppableProps} ref={provided.innerRef}>
{breedList1?.map(({id, name, rank}, index) => {
return (
<Draggable key={id} draggableId={id} index={index}>
{(provided) => (
<li ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<p>
#{rank}: { name }
</p>
</li>
)}
</Draggable>
);
})}
{provided.placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
</div>
)
error: TypeError: Cannot read property 'map' of null
(I am mapping the data later in the program)
const getDogBreeds = async () => {
try {
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
}
useEffect(() => {
getDogBreeds() // -> you are not awaiting this
}, []);
Do this instead
useEffect(() => {
axios.get('https://dog.ceo/api/breeds/list/all')
.then(res => {
const newDogList = generateDogsArray(res.data['message']);
const generatedDogs = selectedDogs(newDogList);
setDogBreeds(generatedDogs);
})
.catch(err => console.log(err));
}, []);
I know this looks awful, but I don't think you should use async/await inside useEffect
Use this in your application
useEffect will update whenever dogBreedsTest is changed. In order to make it work, start with null values and update them to the correct initial values once your async operation is finished.
const { dogBreedsTest } = useApplicationData();
const [dogBreeds, updateDogBreeds] = useState(null);
const [breedList1, updateBreedList1] = useState(null);
useEffect(() => {
updateDogBreeds(dogBreedsTest[0]);
updateBreedList1(dogBreedsTest[0]);
}, [dogBreedsTest]);
The problem is, that react first render and then run useEffect(), so if you don't want to render nothing before the axios, you need to tell to react, that the first render is null.
Where is your map function, to see the code? to show you it?.
I suppose that your data first is null. So you can use something like.
if(!data) return null
2nd Option:
In your map try this:
{breedList1 === null
? null
: breedList1.map(({id, name, rank}, index) => (
<Draggable
key={id} draggableId={id} index={index}>
{(provided) => (
<li ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<p>
#{rank}: { name }
</p>
</li>
)}
</Draggable> ))}
You have null, because your axios is async and react try to render before any effect. So if you say to react that the list is null, react will render and load the data from the api in the second time.
Option 1 use the optional chaining operator
dogBreedsTest?.map()
Option 2 check in the return if dogBreedsTest is an array
retrun (<>
{Array.isArray(dogBreedsTest) && dogBreedsTest.map()}
</>)
Option 3 return early
if (!Array.isArray(dogBreedsTest)) return null
retrun (<>
{dogBreedsTest.map()}
</>)
Option 4 set initial state
const [dogBreedsTest, setDogBreeds] = useState([]);
You could also add a loading state and add a loading spinner or something like that:
const [dogBreedsTest, setDogBreeds] = useState(null);
const [loading, setLoading] = useState(true)
const getDogBreeds = async () => {
setLoading(true)
try{
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
setLoading(false)
}
useEffect(() => {
getDogBreeds()
}, []);
return {
dogBreedsTest,
loading,
setDogBreeds
}
Edit
Try to use a useEffect hook to update the states when dogBreedsTest got set.
const { dogBreedsTest } = useApplicationData()
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest?.[0] ?? []);
const [breedList1, updateBreedList1] = useState(dogBreedsTest?.[0] ?? [])
useEffect(() => {
updateDogBreeds(dogBreedsTest?.[0] ?? [])
updateBreedList1(dogBreedsTest?.[0] ?? [])
}, [dogBreedsTest])

Looping through an object in react

Learning react
Trying to loop through an object from an API call that returns a json object and display it but struggling to implement it
This is the component that should render it
export default class ProfilePage extends Component {
constructor() {
super();
this.state = { data: '' };
}
mapObject(object, callback) {
return Object.keys(object).map(function (key) {
return callback(key, object[key]);
})
}
async componentDidMount() {
const response = await fetch(`https://indapi.kumba.io/webdev/assignment`);
const json = await response.json();
// console.log(json)
this.setState({ data: json });
}
render() {
const data = this.state.data
console.log(data)
return (
<div className="row">
{Object.values(data).map(data => {
<div key={key}>
{data[key]}
</div>
})
}
Woerkkk please
</div>
);
}
}
All I'm getting is a blank screen.
in the console i get the error 'key' is not defined no-undef
You are missing a return statement in your map for your render method.
Edit: Key is not returned from Object.values
Either reconfigure with a return statement like so:
{Object.keys(data).map(key => {
return (<div key={key}>
{data[key]}
</div>);
})
Or alternatively you can implicitly return from arrow function using brackets
{Object.keys(data).map(key => (
<div key={key}>
{data[key]}
</div>)
))
Using Object.values(myObj) you can get all object values as a array. So, with this array, you can iterate over the array and show your items, like this:
{Object.values(myObj).map(value => <p>{value}</p>)}
Don't forget use key prop when iterating.
You can use useState and useEffect to fetch the object data
const App = () => {
const [objData, setObjData] = useState({});
const [objItems, setObjItems] = useState([]);
const fetchObj = async () => {
const response = await fetch(`https://indapi.kumba.io/webdev/assignment`);
const data = await response.json();
setObjData(data);
setObjItems(data.items);
}
useEffect(() => {
fetchObj()
},[]);
return(
<div>
<h1> Order Id :{objData.order_id}</h1>
// or any other objData keys
<h1>Items : </h1>
<ul>
{
objItems.map((i, idx) => {
return(
<li key={idx}>Name : {i.name} , Category: {i.category}, Price: {i.price}, Currency: {i.currency}</li>
)
})
}
</ul>
</div>
)
}
export default App;

transform Class based component to functional based component

guys i wanna convert this code:
export default class App extends Component {
constructor(props) {
super(props);
this.state = { isLoading: true };
}
performTimeConsumingTask = async () => {
return new Promise((resolve) =>
setTimeout(() => {
resolve('result');
}, 2000)
);
};
async componentDidMount() {
const data = await this.performTimeConsumingTask();
if (data !== null) this.setState({ isLoading: false });
}
render() {
if (this.state.isLoading) return <SplashScreen />;
const { state, navigate } = this.props.navigation;
return (something)
i wrote this code but it doesn`t work :
const App = () => {
const [fontLoaded, setFontLoaded] = useState(false);
const [isTimerOn, setIsTimerOn] = useState(true);
if (!fontLoaded) {
return (
<AppLoading
startAsync={fetchFonts}
onFinish={() => setFontLoaded(true)}
/>
);
}
useEffect(async () => {
const data = await performTimeConsumingTask();
if (data !== null) setIsTimerOn(false);
});
if (isTimerOn) return <SplashScreen />;
else {
return (something)
This will show an error :
Invariant Violation: Rendered More Hooks than during the previous render.
If I comment the useEffect hook it will run the splashScreen. Can any one help me in converting it?
Pass [] as an argument if you wanted to use this hook as componentDidMount
useEffect(async () => {
const data = await performTimeConsumingTask();
if (data !== null) setIsTimerOn(false);
}, []);
Here is a list of hooks how you can use hooks to replace lifecycle methods
https://medium.com/javascript-in-plain-english/lifecycle-methods-substitute-with-react-hooks-b173073052a
The Reason for getting an error is your component is rendering too many times and useEffect is also running on each render by passing [] will run the useEffect on first render as it will behave like componentDidMount.
Also follow this to make network calls inside useEffect
https://medium.com/javascript-in-plain-english/handling-api-calls-using-async-await-in-useeffect-hook-990fb4ae423
There must be no conditional return before using all the hooks, in your case you return before using useEffect.
Also useEffect must not run on every render since it sets state in your case. Since you only want it to run on initial render pass an empty array as the second argument.
Also useEffect callback function cannot be async.
Read more about useEffect hook in the documentation.
Check updated code below
const App = () => {
const [fontLoaded, setFontLoaded] = useState(false);
const [isTimerOn, setIsTimerOn] = useState(true);
const performTimeConsumingTask = async () => {
return new Promise((resolve) =>
setTimeout(() => {
resolve('result');
}, 2000)
);
};
useEffect(() => {
async function myFunction() {
const data = await performTimeConsumingTask();
if (data !== null) setIsTimerOn(false);
}
myFunction();
}, []); // With empty dependency it runs on initial render only like componentDidMount
if (!fontLoaded) {
return (
<AppLoading
startAsync={fetchFonts}
onFinish={() => setFontLoaded(true)}
/>
);
}
if (isTimerOn) return <SplashScreen />;
else {
return (something)

Categories