How to use useState hook to map JSON response from API - javascript

My API returns complex json like these.
[
{id: 1, pub_date: "2021-01-06T20:24:57.547721Z"},
{id: 2, pub_date: "2021-01-06T20:24:57.547721Z"},
{id: 3, pub_date: "2021-01-06T20:24:57.547721Z"}
]
So my trial is like this
const [result, setResult] = useState({});
const [result, setResult] = useState(null);
const [result, setResult] = useState([]);
useEffect(() => {
axios.get('http://localhost:8000/api/results/')
.then(res=>{
console.log(res.data); // correctly received
setResult(res.data); // error
console.log(result); // nothing appears
})
.catch(err=>{console.log(err);});
}, []);
However for any try, it shows the error like
Error: Objects are not valid as a React child (found: object with keys {id, pub_date}). If you meant to render a collection of children, use an array instead.
I have some trial and errors.
There is still difficult behaiver to understand.
const [cnt,setCnt] = useState(0);
useEffect(() => {
axios.get('http://localhost:8000/api/results/')
.then((res)=> {
setCnt(2);
console.log(cnt);//shows 0
})
.catch(err=>{console.log(err);});
}, []);
why setCnt is not workd?? I am more confused......

This error comes from your JSX render, where you're certainly trying to display directly your datas from API
useEffect(...)
...
return (
<ul>
{
result.map(r => (
<li key={r.id}>{r.id} - {r.pub_date}</li>
))
}
</ul>
)

If you are calling setResult(res.data), then your result state should be of type [].
import React, { useEffect, useState } from "react";
const fetchData = () =>
Promise.resolve({
data: [
{ id: 1, pub_date: "2021-01-06T20:24:57.547721Z" },
{ id: 2, pub_date: "2021-01-06T20:24:57.547721Z" },
{ id: 3, pub_date: "2021-01-06T20:24:57.547721Z" }
]
});
const ListItem = ({ id, pub_date }) => (
<li>
{id} — {pub_date}
</li>
);
const ListItems = ({ items }) => (
<ul>
{items.map((props) => (
<ListItem key={props.id} {...props} />
))}
</ul>
);
const App = () => {
const [result, setResult] = useState([]);
useEffect(() => {
fetchData().then((res) => {
setResult(res.data);
});
}, []);
return (
<div className="App">
<ListItems items={result} />
</div>
);
};
export default App;

Related

React: Cannot read property 'map' of undefined with an API

I have a simple list in React where I'm fetching data from an array, and it's working.
But now that I want to fetch data from an external API, I have the following error
Cannot read property 'map' of undefined
I tried replacing .data with .json() but didn't work.
https://codesandbox.io/s/silly-taussig-e3vy7?file=/src/App.js:561-571
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import axios from "axios";
export default () => {
const initialList = [
{
id: "1",
name: "John"
},
{
id: "2",
name: "Doe"
},
{
id: "3",
name: "Seb"
}
];
const [list, setList] = React.useState([]);
const [name, setName] = React.useState("");
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/users/")
.then((response) => {
setList(response.data.list);
})
.catch((err) => console.log(err));
}, []);
function handleChange(event) {
setName(event.target.value);
}
function handleAdd() {
const newList = list.concat({ name });
setList(newList);
setName("");
}
return (
<div>
<div>
<input type="text" value={name} onChange={handleChange} />
<button type="button" onClick={handleAdd}>
Add
</button>{" "}
</div>
<ul>
<div>
{list.map((item, index) => (
<li key={item.id}>
<div>{item.name}</div>
</li>
))}
</div>
</ul>
</div>
);
};
The result of your API doesn't have data.list
Try instead:
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/users/")
.then((response) => {
setList(Object.values(response.data));
})
.catch((err) => console.log(err));
}, []);
Issue is with the way you are processing the response from the XHR call. The data returned by the URL https://jsonplaceholder.typicode.com/users/ is an array. There is no element called list in the data. So when you do response.data.list, the list variable gets set to undefined.
PFB revised code
https://codesandbox.io/s/hungry-tdd-xjw6z
...
axios
.get("https://jsonplaceholder.typicode.com/users/")
.then((response) => {
setList(response.data); //Change here
})
.catch((err) => console.log(err));
}, []);
...

TypeError: Cannot read property 'map' of undefined. React

I am new to React and I am having a problem.
How to iterate over an array in an array?
When trying to iterate over an array using the map() method, an error occurs
my code:
import React, {useState, useEffect} from 'react';
import Header from '../modules/header/header';
import Footer from '../modules/footer/footer';
import './pages.scss';
function PageWorksItem({ match, location }) {
const { params: { id } } = match;
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [works, setItems] = useState([]);
useEffect(() => {
let cleanupFunction = false;
fetch(`http://localhost:3001/works/${id}`)
.then(res => res.json())
.then(
(result) => {
console.log(result);
setIsLoaded(true);
if(!cleanupFunction) setItems(result);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
return () => cleanupFunction = true;
}, [])
if (error) {
return <div className="works error">Error: {error.message}</div>;
} else if (!isLoaded) {
return <div className="works loading">. . .</div>;
} else {
return (
<>
<Header />
<div className="works item">
{works.tags.map(item => (
<li>{item}</li>
))}
</div>
<Footer />
</>
);
}
}
export default PageWorksItem;
JSON fragment:
{
"works": [
{
"id": 1,
"name": "21 one line SPA images",
"cat": "Illustrations",
"tags": ["one", "two", "free"]
}
]
}
if you specify {works.tags} or with an index {works.tags[0]} - everything works, but if you iterate over the array, an error occurs.
You are using works.tag.map. But the initialization value of works is an empty array []:
const [works, setItems] = useState([]);. So works.tag is undefined and this error occurred.
You can fix this issue by add optional chaining like this:
{works.tags?.map((item, index)=> (
<li key={index}>{item}</li>
))}
Note: You need to add unique key in child componet when using map

Displaying Nested JSON in React/JSX

I have some JSON that is formatted like this:
{
card_id: "afe1500653ec682b3ce7e0b9f39bed89",
name: "A.J. Burnett",
playerattribute: {
team: "Marlins",
rarity: "Diamond",
}
}
I'm attempting to display the name and the team in a component. Here is what I have.
const PlayerProfile = ({ match, location }) => {
const { params: { cardId } } = match;
const [player, setPlayer] = useState(0);
useEffect(() => {
const fetchData = async () => {
const result = await axios(
`http://127.0.0.1:8000/api/player-profiles/${cardId}/?format=json`,
).then((result) => {
setPlayer(result.data);
});
};
fetchData();
}, []);
return (
<Container component="main">
Name: {player.name}
Team: {player.playerattribute.team}
</Container>
)
}
export default PlayerProfile;
However, I get this error: TypeError: Cannot read property 'team' of undefined
The name works fine. So I'm assuming it's an issue with the nested JSON.
You probably shouldn't instanciate your player state with 0 if the projected value is an object.
The error comes up because you try to access a property of an object property that doesn't exist at creation.
Basically, your code tries to do this: {0.playerattribute.team}
0.playerattribute => undefined
Workaround would be a conditionnal render or a default initial value of your state that matches the JSX needs.
const PlayerProfile = ({ match, location }) => {
const { params: { cardId } } = match;
const [player, setPlayer] = useState({
name: "",
playerattribute: {
team: ""
}
});
useEffect(() => {
const fetchData = async () => {
const result = await axios(
`http://127.0.0.1:8000/api/player-profiles/${cardId}/?format=json`,
).then((result) => {
setPlayer(result.data);
});
};
fetchData();
}, []);
return (
<Container component="main">
Name: {player.name}
Team: {player.playerattribute.team}
</Container>
)
}
export default PlayerProfile;
or
const PlayerProfile = ({ match, location }) => {
const { params: { cardId } } = match;
const [player, setPlayer] = useState(null);
useEffect(() => {
const fetchData = async () => {
const result = await axios(
`http://127.0.0.1:8000/api/player-profiles/${cardId}/?format=json`,
).then((result) => {
setPlayer(result.data);
});
};
fetchData();
}, []);
return (
<Container component="main">
Name: {player?.name}
Team: {player?.playerattribute?.team}
</Container>
)
}
export default PlayerProfile;
Set useState const [player, setPlayer] = useState("");
const [player, setPlayer] = useState({
Name: '',
Team: ''
}}
//on your setPlayer you may
const playerData = result.data;
setPlayer({
Name: playerData.name
Team: playerData.playerattribute.team})
if you still getting same error, please provide screenshot of console.log(result)

Why my App keeps rerendering on any action?

I noticed this strange behavior of my App, that when I do anything on it (write something in the search field, create a new list, etc) my page gets rerendererd. Of course, I cannot find the source of it.
Below is the the look of my page, when it is loaded the first time, with default (blank) search results.
And now, the result in profiler, when I type something in the searchBar (or create a new list, or anything):
Here is my code of the App.js
import React, { useState, createContext, useEffect } from "react";
import NavBar from "../NavBar/NavBar";
import youtube from "../../apis/youtube";
import VideoList from "../VideoList/VideoList";
import VideoDetail from "../VideoDetail/VideoDetail";
import SideBar from "../SideBar/SideBar";
import "./App.css";
export const VideoContext = createContext();
export const FavoriteContext = createContext();
const API_KEY = process.env.REACT_APP_API_KEY;
const App = () => {
const [ videos, setVideos ] = useState([]);
const [ searchedValue, setSearchedValue ] = useState({
selectedVideo: null
});
const handleSelectedVideo = (singleRenderedVideo) => {
setSearchedValue((previous) => ({
...previous,
selectedVideo: singleRenderedVideo
}));
};
const handleSearch = async (inputText) => {
const response = await youtube.get("/search", {
params: {
q: inputText,
part: "snippet",
type: "video",
maxResults: 16,
key: API_KEY
}
});
setVideos(response.data.items);
setSearchedValue({
selectedVideo: response.data.items[0] //take the first search result and make it appear as a playable one
});
};
useEffect(() => {
handleSearch();
}, []);
//By the user newly created lists
const [ lists, setLists ] = useState([]);
const addList = (newList) => {
setLists((prevLists) => {
return [ ...prevLists, newList ];
});
};
const onDeleteList = (id) => {
setLists((prevLists) => {
return prevLists.filter((listItem, index) => {
return index !== id;
});
});
};
//Render(Play) Favorited Video
const [ favoritedItem, setFavoritedItem ] = useState({
clickedFavoritedVideo: null
});
const handleSelectedFavorite = (renderFavorite) => {
setFavoritedItem((previous) => ({
...previous,
clickedFavoritedVideo: renderFavorite
}));
};
//Add a newly favorited video to a, by user created, list (BUG: for now the favorited video is added to EVERY, by the user, created list)
const [ favoritedList, setFavoritedList ] = useState([]);
const handleFavoritedVideo = (favoritedElement, selectedList) => {
setFavoritedList((previousFavorited) => {
return [ { favoritedElement, selectedList }, ...previousFavorited ];
});
};
const deleteFavorited = (id) => {
setFavoritedList((prevLists) => {
return prevLists.filter((listItem, index) => {
return index !== id;
});
});
};
return (
<div className="container">
<NavBar handleSearch={handleSearch} />
<div className="content">
<SideBar
addList={addList}
lists={lists}
handleSelectedFavorite={handleSelectedFavorite}
favoritedList={favoritedList}
onDeleteList={onDeleteList}
onDeleteFavorited={deleteFavorited}
/>
<main className="video">
<VideoContext.Provider value={handleSelectedVideo}>
<FavoriteContext.Provider value={handleFavoritedVideo}>
<VideoDetail
selectedVideo={searchedValue.selectedVideo}
clickedFavoritedVideo={
favoritedItem.clickedFavoritedVideo
}
/>
<VideoList listOfVideos={videos} lists={lists} />
</FavoriteContext.Provider>
</VideoContext.Provider>
</main>
</div>
</div>
);
};
export default App;
I will not post my whole app here, because it is a lot of files. I just give a link to my gitHub:
GitHub LINK
I was trying to find a solution, as stated here:
Link to SO page
which is like my case, but it didn't help (maybe because I was not using memo):
import React, { useState, createContext, useEffect, useCallback } from "react";
import NavBar from "../NavBar/NavBar";
import youtube from "../../apis/youtube";
import VideoList from "../VideoList/VideoList";
import VideoDetail from "../VideoDetail/VideoDetail";
import SideBar from "../SideBar/SideBar";
import "./App.css";
export const VideoContext = createContext();
export const FavoriteContext = createContext();
const API_KEY = process.env.REACT_APP_API_KEY;
const App = () => {
const [ videos, setVideos ] = useState([]);
const [ searchedValue, setSearchedValue ] = useState({
selectedVideo: null
});
const handleSelectedVideo = useCallback((singleRenderedVideo) => {
setSearchedValue((previous) => ({
...previous,
selectedVideo: singleRenderedVideo
}));
}, []);
const handleSearch = async (inputText) => {
const response = await youtube.get("/search", {
params: {
q: inputText,
part: "snippet",
type: "video",
maxResults: 16,
key: API_KEY
}
});
setVideos(response.data.items);
setSearchedValue({
selectedVideo: response.data.items[0] //take the first search result and make it appear as a playable one
});
};
useEffect(() => {
handleSearch();
}, []);
//By the user newly created lists
const [ lists, setLists ] = useState([]);
const addList = useCallback((newList) => {
setLists((prevLists) => {
return [ ...prevLists, newList ];
});
}, []);
const onDeleteList = useCallback((id) => {
setLists((prevLists) => {
return prevLists.filter((listItem, index) => {
return index !== id;
});
});
}, []);
//Render(Play) Favorited Video
const [ favoritedItem, setFavoritedItem ] = useState({
clickedFavoritedVideo: null
});
const handleSelectedFavorite = useCallback((renderFavorite) => {
setFavoritedItem((previous) => ({
...previous,
clickedFavoritedVideo: renderFavorite
}));
}, []);
//Add a newly favorited video to a, by user created, list (BUG: for now the favorited video is added to EVERY, by the user, created list)
const [ favoritedList, setFavoritedList ] = useState([]);
const handleFavoritedVideo = useCallback((favoritedElement, selectedList) => {
setFavoritedList((previousFavorited) => {
return [ { favoritedElement, selectedList }, ...previousFavorited ];
});
}, []);
const deleteFavorited = useCallback((id) => {
setFavoritedList((prevLists) => {
return prevLists.filter((listItem, index) => {
return index !== id;
});
});
}, []);
return (
<div className="container">
<NavBar handleSearch={handleSearch} />
<div className="content">
<SideBar
addList={addList}
lists={lists}
handleSelectedFavorite={handleSelectedFavorite}
favoritedList={favoritedList}
onDeleteList={onDeleteList}
onDeleteFavorited={deleteFavorited}
/>
<main className="video">
<VideoContext.Provider value={handleSelectedVideo}>
<FavoriteContext.Provider value={handleFavoritedVideo}>
<VideoDetail
selectedVideo={searchedValue.selectedVideo}
clickedFavoritedVideo={
favoritedItem.clickedFavoritedVideo
}
/>
<VideoList listOfVideos={videos} lists={lists} />
</FavoriteContext.Provider>
</VideoContext.Provider>
</main>
</div>
</div>
);
};
export default App;
I also tried to give a type for my buttons (type="button"), which currently have no type, like as in:
CreateNewList.js
import React, { useState } from "react";
import iconSprites from "../../images/sprite.svg";
import shortid from "shortid";
const CreateNewList = ({ onAdd }) => {
const [ list, setList ] = useState({
id: shortid.generate(),
title: ""
});
const handleChange = (event) => {
const { value } = event.target;
setList((prevList) => {
return {
...prevList,
title: value
};
});
event.preventDefault();
};
const submitNewList = (event) => {
onAdd({ ...list });
setList({ id: shortid.generate(), title: "" });
event.preventDefault();
};
return (
<React.Fragment>
<li className="new-list__item">
<form>
<div className="new-list__link">
<button
onClick={submitNewList}
className="new-list__btn-plus btn"
>
<svg className="new-list__icon">
<use href={iconSprites + "#icon-circle-with-plus"} />
</svg>
</button>
<input
className="new-list__input"
name="title"
value={list.title}
onChange={handleChange}
placeholder="New List"
/>
</div>
</form>
</li>
</React.Fragment>
);
};
export default CreateNewList;
but it also didn't help. Maybe because they are not in <form>?
So that is it. Maybe someone can help me with my issue?
To have all of the questions in one place:
Why my app keeps rerendering?
Should I use memo with useCallback?
Should I put my buttons in a <form> and give them a type?

react-hooks/exhaustive-deps causing dependency warning, fix hangs code

Im working on a project and have incorporated Hooks for the first time. When using the useEffects and useState hooks, Im encountering a wierd warning from eslint.
My Code:
import React, { useState, useEffect } from 'react';
import { Card } from 'antd';
import Search from 'antd/lib/input/Search';
import { IPatient } from 'types/IPatient';
const SearchBox = () => {
const [searchTerm, setSearchTerm] = useState('');
const [searchResults, setSearchResults] = useState<IPatient[]>([]);
const handleChange = (event: any) => {
setSearchTerm(event.target.value);
};
const cards: IPatient[] = [
{
id: 1,
name: 'Erling',
description: ['tall', 'male', 'sick'],
age: 98,
isHuman: true,
},
// other data...
];
useEffect(() => {
const results: IPatient[] = cards.filter((card) =>
card.name.toLowerCase().includes(searchTerm),
);
setSearchResults(results);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchTerm]);
return (
<>
<div className="searchbox">
<Search onChange={handleChange} />
</div>
<div>
{searchResults.map((data) => (
<Card key={data.id} hoverable>
<h1>{data.name}</h1>
<p>Patient ID: {data.id} </p>
<p>Age: {data.age} years old.</p>
<p>
Description:{' '}
{data.description[0] +
' ' +
data.description[1] +
' ' +
data.description[2]}
</p>
</Card>
))}
</div>
</>
);
};
export default SearchBox;
Now, the issue is that eslint is calling an error on my dependency array, and if I put both variables (cards and searchTerms) inside the array, it results in the code hanging and the webapp crashing. The eslint-line is currently in place to suppress the warning, but this is less than ideal.
So I guess my question is how to circumvent this. I am sure this a Beginners mistake, as it is my first time with Hooks. Any help would be appreciated!
The problem when adding cards to the dependency array is that you are creating a new reference of cards array on each rerender and hence the useEffect runs again, causing an infinite loop
Since Card array seems to be const you can take it out of functional component and then add it to dependency array
const cards: IPatient[] = [
{
id: 1,
name: 'Erling',
description: ['tall', 'male', 'sick'],
age: 98,
isHuman: true,
},
// other data...
];
const SearchBox = () => {
const [searchTerm, setSearchTerm] = useState('');
const [searchResults, setSearchResults] = useState<IPatient[]>([]);
const handleChange = (event: any) => {
setSearchTerm(event.target.value);
};
useEffect(() => {
const results: IPatient[] = cards.filter((card) =>
card.name.toLowerCase().includes(searchTerm),
);
setSearchResults(results);
}, [searchTerm, cards]);
...

Categories