React Typescript fetch: useState UseEffect pokeapi - reactjs

I am trying to learn how to fetch data and display in a table/list/graph in React.
I extracted the fetch to a component and while i can get the list to appear i think this is wrong - Why and how to fix?
// getData.tsx
import React, { useState, useEffect } from 'react';
let myArray: string[] = [];
export default function GetData() {
const [info, setData] = useState([]);
useEffect(() => {
getMyData();
}, []);
const getMyData = async () => {
const response = await fetch('https://pokeapi.co/api/v2/type')
const data =await response.json();
//console.log(data.results)
for (var i = 0; i < data.results.length; i++) {
myArray.push(data.results[i].name)
setData(data.results[i].name)
}
console.log(info)
}
return (
<div>
<h1>get Data</h1>
{myArray.map((value,index) => {
return <li key={index}>{value}</li>;
})}
</div>
)
}
Also same issue but do not understand why the names and Array don't both work?
export default function GetData(){
const names: string[] = ["whale", "squid", "turtle", "coral", "starfish"];
const theArray: string[] = [];
const getData = async () => {
const response = await fetch('https://pokeapi.co/api/v2/type');
const data = await response.json()
//for (var i = 0; i < data.results.length; i++) {
for (var i = 0; i < 5; i++) {
theArray.push(data.results[i].name)
}
console.log(theArray)
}
console.log(names)
console.log(theArray)
getData()
return (
<div>
<ul>{names.map(name => <li key={name}> {name} </li>)}</ul>
<h1>get Data</h1>
<ul>{theArray.map(name => <li key={name}> {name} </li>)}</ul>
</div>
)
}

You aren't using the state data... The issue is that.
The correct way to do this:
const [data, setData] = useState([])
useEffect(() => {
fetch('https://pokeapi.co/api/v2/type')
.then(res => res.json())
.then(setData)
},[])
return <div>
<ul>
{data.map((name) => <li key={name}>{name}</li>}
</ul>
</div>

The problem is getData is declared as async function. That means it's returning Promise that you can await on and get it's result. But you never do that. You're using it without await essentially not waiting for it finish and discarding its result.
To get the result of async function you should await on it. In your second component you'll have to write this:
...
console.log(names)
console.log(theArray)
await getData() // add 'await' to well... wait for the result of the getData execution
return (
...
But you can await only inside async function aswell. As far as I'm concerned you're not able to use async components now (react#16-17). So the second component is not going to work as intended. At least untill react is able to support async components.
Though there are some issues even with your first component.
let myArray: string[] = [];
Declared in the module scope it will be shared (and not reseted) between all instances of your component. That may (and will) lead to very unexpected results.
Also it's quite unusuall you don't get linting errors using getMyData before declaring it. But I suppose that's just an artefact of copy-pasting code to SO.
Another problem is you're using setData inside your component no to set the contents of myArray but to trigger rerender. That's quite brittle behavior. You should directly set new state and react will trigger next render and will use that updated state.
To work properly your first component should be written as:
import React, { useState, useEffect } from 'react'
export default function GetData() {
const [myArray, setMyArray] = useState([])
const getMyData = async () => {
const response = await fetch('https://pokeapi.co/api/v2/type')
const data = await response.json()
const names = data.results.map((r) => r.name) // extracting 'name' prop into array of names
setMyArray(names)
}
useEffect(() => {
getMyData();
}, []);
return (
<div>
<h1>get Data</h1>
{myArray.map((value,index) => (
<li key={`${index}-${value}`}>{value}</li>
))}
</div>
)
}

Related

Render page on react after loading json

I have load() function:
async function load() {
let url = `www.com/file.json`
let data = await (await fetch(url)).json()
return data
}
I need to render my page after loading my json file from server:
export const Page = async () => {
const data_ = await load()
return (
<div className="page">
content
</div>
)
}
How can i do it?
You can use the useEffect hook to make the call when the component mounts, it will be called once and fetch the data you need, then it will set it in the data state to be used in the return inside the divs.
As you did not provide the structure of the response I cannot give a more detailed explanation on how to render the data itself. Also id_ has no use in your example but I kept it there to closely resemble your example.
import React, {useState, useEffect} from "react";
export const Page = () => {
const [data, setData] = useState(null)
useEffect(() => {
const load = async (id_) => {
let url = `www.com/file.json`
let data = await (await fetch(url)).json()
const manipulatedData = ...
// do manipulation
setData(manipulatedData)
}
load()
}, [])
return (
<div className="page">
{data ? data : null}
</div>
);
}
export default Page;
You need to use useEffect and useState
Here's how you could achieve this:
import {useState, useEffect} from 'react';
export const Page = () => {
const [data, setData] = useState();
useEffect(() => {
async function load() {
let url = `www.com/file.json`;
let data = await (await fetch(url)).json();
setData(data);
}
load();
}, []);
return <div className="page">{JSON.stringify(data)}</div>;
}
Replace JSON.stringify with data.something to show a particular field in the data
A couple of tips:
React components cannot be async functions
useState carries variables you need to render the page
useEffect is a function that lets your component handle calling async code or any other kind of side effect
You can use the hook useEffect() & useState() to load your data :
function load() {
let url = `www.com/file.json`
let data = await (await fetch(url)).json()
return data
}
export const Page = async () => {
const [data, setData] = useState(null)
useEffect(() => {
load().then((_data) => {
setData(_data)
})
}, [])
if (!data) {
return <div>loading data...</div>
}
return (
<div className="page">
data is ready to use !
</div>
)
}

Getting map data using React from firestore

I am having trouble trying to figure out how to get map data from Firestore in reactjs. My code keeps erroring saying "Objects are not valid as a React". Can someone point me to an example or show me one with my database below?
import React, { useState, useEffect } from "react";
import { firestore } from "../../../FireBase/FireBase";
import CartItem from "./CartItem";
const CartPage = (props) => {
const [cart, setCart] = useState(null);
useEffect(() => {
const fetchCart = async () => {
const doc = await firestore
.collection("Users")
.doc("CfL5uszL3CTE1nIQTgDrKK5q4OV2")
.get();
const data = doc.data();
console.log("data " + data);
if (!data) {
// document didn't exist
console.log("hit null");
setCart(null)
} else {
console.log("hit");
setCart(data.cart);
}
console.log("cart " + cart);
}
fetchCart();
}, []);
if (!cart) {
// You can render a placeholder if you like during the load, or just return null to render nothing.
return null;
}
return (
<div className="cartpage">
<h1>cart</h1>
<div className="cart">
{cart.map(cartItem => (
<div key={cartItem.id}>{cartItem.name}</div>
))}
</div>
</div>
);
};
export default CartPage;
The error your getting is because you're returning a promise from your component (You've made it an async function, and async functions return promises). Promises and other arbitrary objects cannot be returned from rendering in react. You need to have a state variable for holding your data. On the first render, you'll have no data, and then you'll use a useEffect to fetch the data and update the state
Additionally, you have some mistakes with how you're trying to get the data and access it. You're calling .get("Cf...V2"), but .get doesn't take a parameter. If you want to specify which document to get, you use the .doc() function for that. .get() will then return a promise, so you need to await that before trying to access any properties on it. The data you get will be an object with all the properties on the right hand side of your screenshot, and you will need to pluck the cart property out of that.
In short, i recommend something like the following:
const CartPage = (props) => {
const [cart, setCart] = useState(null);
useEffect(() => {
const fetchCart = async () => {
const doc = await firestore
.collection("Users")
.doc("CfL5uszL3CTE1nIQTgDrKK5q4OV2")
.get();
const data = doc.data();
if (!data) {
// document didn't exist
setCart(null)
} else {
setCart(data.cart);
}
}
fetchCart();
}, []);
if (!cart) {
// You can render a placeholder if you like during the load, or just return null to render nothing.
return null;
}
return (
<div className="cartpage">
<h1>cart</h1>
<div className="cart">
{cart.map(cartItem => (
<div key={cartItem.id}>{cartItem.name}</div>
))}
</div>
</div>
);
};
I don't think so that you can create async component in this way. What you return in your component should be simple JSX code. If you want to do something asynchronously inside component you should wrap this inside useEffect hook.
const CartPage = (props) => {
const [ cart, setCart ] = useState(null)
useEffect(() => {
const inner = async () => {
const ref = await firestore
.collection("Users")
.get("CfL5uszL3CTE1nIQTgDrKK5q4OV2").cart;
setCart(
ref.map((item) => ({
id: item.id,
name: item.name
}))
);
};
inner();
}, []);
return (
<div className="cartpage">
<h1>cart</h1>
<div className="cart"></div>
</div>
);
};

Cannot read property 'map' of undefined on useState value

I'm new to react, I'm getting this error constantly and after google some I can't find the reason why the useState value can't be read as array :( ... this the error I'm getting: 'TypeError: team.map is not a function'
import React, { useEffect, useState } from "react";
const SportTeams = () => {
const [team, setTeam] = useState([]);
useEffect(() => {
const getSports = async () => {
const response = await fetch("https://www.thesportsdb.com/api/v1/json/1/all_sports.php");
const data = await response.json();
setTeam(data);
console.log(data);
}
getSports();
}, []);
return (
<div className="myClass">
<ul>
{team.map((sport, index) => {
return <li key={`${sport.strSport}-${index}`}>{sport.strSport}</li>
})}
</ul>
</div>
);
};
export default SportTeams;
Just update setTeam like following, your error will be resolved.
setTeam(data.sports);
It is because you are setting the team state with the data without checking if its undefined. If the data is undefined your state team become undefined as well. So make sure to check the data.
import React, { useEffect, useState } from "react";
const SportTeams = () => {
const [team, setTeam] = useState([]);
useEffect(() => {
const getSports = async () => {
const response = await fetch("https://www.thesportsdb.com/api/v1/json/1/all_sports.php");
if (response) {
const data = await response.json();
if (data) {
setTeam(data);
}
}
console.log(data);
}
getSports();
}, []);
return (
<div className="myClass">
<ul>
{team.map((sport, index) => {
return <li key={`${sport.strSport}-${index}`}>{sport.strSport}</li>
})}
</ul>
</div>
);
};
export default SportTeams;
There might also be the chance that your response is not what you expected and the actual data might be inside your response. In that case you need check what your response first then proceed to set the data.
As I said in my comment. the value you are setting to teams isn't an array.
const data = await response.json();
setTeam(data.sports);

useEffect causing .map() error when sending post request

When I try adding a new post I get this error. But when I console.log({ newPost }) I see that the new obj gets added. Something is happening when I try setting it in setPost({ newPost }).
import React, { useState, useEffect } from "react"
import axios from "axios"
const Users = () => {
const [posts, setPosts] = useState([])
useEffect(() => {
const getPosts = async () => {
const { data: post } = await axios.get(
"https://jsonplaceholder.typicode.com/posts"
)
setPosts(post)
}
getPosts()
}, [])
const handlePost = async () => {
const obj = {
title: "foo",
body: "bar",
userId: 1,
}
const { data: post } = await axios.post(
"https://jsonplaceholder.typicode.com/posts",
obj
)
const newPost = [post, ...posts]
setPosts({ newPost })
}
return (
<div>
<h1>List of all Posts</h1>
<button onClick={() => handlePost()}>Post new title</button>
<ul className="list-group list-group-flush">
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
)
}
export default Users
The error is that you are calling setPosts with an object when you mean it to be an array.
Replace
setPosts({ newPost })
With
setPosts( newPost )
This part doesn't really matter, but I would say that when dealing with async methods it's a better practice to setState with a function of the previousState rather than accessing the previous state directly.
setPosts((posts) => [post, ...posts])
posts state variable is expected to be an Array, so there's no need to set it inside an object. Change setPosts({ newPost }) to setPosts(newPost). It should work.
I run the code with the error in my pc so there are some errors.
But I changed setPosts(newPost).
So it is working very well.
If we avoid these errors, we should be familiar with React Hooks

React .map an object

I've got an eslint error Property 'date' does not exist on type 'never', and it's on data.date. Any idea how can I fix it?
import React from 'react'
import app from './../Flamelink'
function App() {
const [data, setData] = React.useState([])
React.useEffect(() => {
const fetchData = async () => {
const data = await app.content.get({
schemaKey: 'persons'
})
setData(data)
}
fetchData()
}, [])
return (
<ul>
{data.map(data => (
<li>{data.date}</li>
))}
</ul>
)
}
export default App
First of all, I would use a different variable name instead of just calling everything data. This way you avoid variable shadowing errors.
If you want your state variable to be data then call the answer you get from your fetch something else, result maybe. Then, when mapping your data state, call the current value something else too, maybe item or dataItem.
Second, since you appear to be using TypeScript, you need to tell TS the structure of your data state array.
Try this:
function App() {
const [data, setData] = React.useState<Array<{date: string}>>([]);
React.useEffect(() => {
const fetchData = async () => {
const result = await app.content.get({
schemaKey: "persons"
});
setData(result);
};
fetchData();
}, []);
return (
<ul>
{data.map(item=> (
<li>
{item.date}
</li>
))}
</ul>
);
}

Resources