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;
Related
I want to print out the crawled data from the site I want using Gatsby. But I don't know why this error appears.
here's my crawler
class Crawler {
constructor() {
this.client = axios.create();
}
async crawlNews() {
const url = 'https://finance.naver.com/news/news_list.naver?mode=RANK';
const settedResult = await this.client
.get(url, { responseType: 'arraybuffer' })
.then((response) => {
const setResult = [];
const content = iconv.decode(response.data, 'EUC-KR');
const $ = cheerio.load(content);
$('.simpleNewsList > li').each((i, el) => {
const title = $(el).text();
setResult.push({
id: parseInt(i) + 1,
title: title
.replace(/(\r\n|\n|\r|\t)/gm, '')
.toString(),
});
});
return setResult;
})
.catch((err) => console.error(err));
return settedResult;
}
}
and here's Slide component
import React from 'react';
export function Slide(props) {
const { index, title } = props;
return (
<div>
{index} | {title}
</div>
);
}
here's pages/index.js in gatsby
async function Home() {
const settedResult = new Crawler();
const dataSource = await settedResult.crawlNews();
const result = dataSource.map((obj) => {
<Slide index={obj.id} title={obj.title} />;
});
return <div>{result}</div>;
}
export default Home;
When I run 'gatsby develop' with the above files, an error like the title appears
Maybe you can provide a stackblitz ? Its seems you'r missing a return here :
const result = dataSource.map((obj) => {
<Slide index={obj.id} title={obj.title} />;
});
This should work
const result = dataSource.map((obj) =>
<Slide index={obj.id} title={obj.title} />
);
or
const result = dataSource.map((obj) => {
return <Slide index={obj.id} title={obj.title} />;
};
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])
I have written the following codes but the console prompts an error that says 'this.state.result.map is not a function' (in the 'return'). Just wondering if anyone has any idea what is wrong. Thanks!
class App extends React.Component {
constructor (props) {
super(props);
this.state = {
result: []
}
}
componentDidMount(){
const fetchGithub = (loginName) =>
fetch(`https://api.github.com/users/${loginName}/followers`)
.then(response => response.json());
const processJson= () =>
fetchGithub('ericelliott')
.then(json => {
const userList = json.map(user =>
user.login
)
const result = userList.join(', ')
this.setState({result: result})
});
processJson();
}
render(){
return (
<div>
{this.state.result.map(user => (
<div>
<div>
{user.login}
</div>
</div>
))}
</div>
)
}
}
JS map is reserved for Arrays, as you are using const result = userList.join(', '). This turns your result array to a string therefore map function is no longer available. Just remove that line.
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>
)
}
I'm making two calls from an API. I want to display the top results for airing shows and top tv shows. I have all of the data being returned from both API calls, but my code isn't efficient. I'd like to somehow take my returned data and display it in a single component (TopAnime) that will then map and return the information provided.
I figured reduce would be the best route, but I'm fumbling at this point. My thought process was to reduce the returned data from the API into an array. Take that reduced array and pass it as my new state and then have my component display it without having to write duplicate code. Both topTv and topAIring are showing because I've written the component twice, but it's clearly not best practice to repeat code.
class HomePage extends Component {
state = {
topTv: [],
topAiring: []
}
async getData() {
const api = "https://api.jikan.moe/v3"
const urls = [
`${api}/top/anime/1/tv`,
`${api}/top/anime/1/airing`
];
return Promise.all(
urls.map(async url => {
return await fetch(url) // fetch data from urls
})
)
.then(responses => // convert response to json and setState to retrieved data
Promise.all(responses.map(resp => resp.json())).then(data => {
console.log("data", data)
// const results = [...data[0].top, ...data[1].top]; // data from TV & data from airing
const reduceResults = data.reduce((acc, anime) => {
return acc + anime
}, [])
console.log('reduce', reduceResults);
const tvResults = data[0].top // data from TV
const airingResults = data[1].top // data from airing
this.setState({
topTv: tvResults,
topAiring: airingResults
});
})
)
.catch(err => console.log("There was an error:" + err))
}
componentDidMount() {
this.getData();
}
render() {
return (
<HomeWrapper>
<h2>Top anime</h2>
<TopAnime>
{this.state.topTv.map((ani) => {
return (
<div key={ani.mal_id}>
<img src={ani.image_url} alt='anime poster' />
<h3>{ani.title}</h3>
</div>
)
}).splice(0, 6)}
</TopAnime>
<h2>Top Airing</h2>
<TopAnime>
{this.state.topAiring.map((ani) => {
return (
<div key={ani.mal_id}>
<img src={ani.image_url} alt='anime poster' />
<h3>{ani.title}</h3>
</div>
)
}).splice(0, 6)}
</TopAnime>
</HomeWrapper>
)
}
}
Since the response from API contains a flag called rank you can use the Array.prototype.filter to only show shows ranked 1-6.
Working demo here
import React, { Component } from "react";
import { TopAnime } from "./TopAnime";
export class HomePage extends Component {
state = {
topTv: [],
topAiring: []
};
async getData() {
const api = "https://api.jikan.moe/v3";
const urls = [`${api}/top/anime/1/tv`, `${api}/top/anime/1/airing`];
return Promise.all(
urls.map(async url => {
return await fetch(url); // fetch data from urls
})
)
.then((
responses // convert response to json and setState to retrieved data
) =>
Promise.all(responses.map(resp => resp.json())).then(data => {
// if you care about mutation use this
const topTvFiltered = data[0].top.filter( (item) => item.rank <= 6 );
const topAiringFiltered = data[1].top.filter( (item) => item.rank <= 6 );
this.setState({
topTv: topTvFiltered,
topAiring: topAiringFiltered
});
})
)
.catch(err => console.log("There was an error:" + err));
}
componentDidMount() {
this.getData();
}
render() {
const { topTv, topAiring } = this.state;
return (
<React.Fragment>
{ topTv.length > 0 ? <h2>Top TV</h2> : null }
{this.state.topTv.map((item, index) => (
<TopAnime key={index} title={item.title} image={item.image_url} />
))}
{ topAiring.length > 0 ? <h2>Top airing</h2> : null }
{this.state.topAiring.map((item, index) => (
<TopAnime key={index} title={item.title} image={item.image_url} />
))}
</React.Fragment>
);
}
}