React: API call fails when I refresh the page - javascript

Here is my code:
import Base from "./Base";
import axios from "axios";
import { createData } from "../../utils";
import { useState, useEffect } from "react";
export default function Today(props) {
const [scoops, setScoops] = useState(0);
//Fetch api/scoops/today
const fetchScoops = async () => {
const res = await axios.get("http://localhost:8000/api/scoops/today/");
setScoops(res.data);
};
useEffect(() => {
fetchScoops();
}, []);
console.log(scoops[0]);
const rows = [
createData(0, 1, scoops[0].title, "http://example.com"),
createData(1, 2, "Paul McCartney", "http://example.com"),
createData(2, 3, "Tom Scholz", "http://example.com"),
createData(3, 4, "Michael Jackson", "http://example.com"),
createData(4, 5, "Bruce Springsteen", "http://example.com"),
];
return <Base rows={rows} duration="Today" />;
}
Here is what the console returns:
> undefined
> Today.js:20 {url: 'http://localhost:8000/api/scoops/1/', title: 'Hello World!', rank: 0, created_at: '2021-10-05T04:44:52.027336Z', updated_at: '2021-10-05T04:44:52.027336Z'}
The problem is when I refresh the page, I get the following error message:
TypeError: Cannot read properties of undefined (reading 'title')
Help would be much appreciated!
Update:
Optional chaining solved the issue, this works great.
scoops[0]?.title

You can set the initial scoops state to []
const [scoops, setScoops] = useState([]);
render scoops when data fetched using conditionalRendering
return <> {scoops.lenght > 0 && <Base rows={rows} duration="Today" />} </>;

The problem is, you are initializing scoops as 0 but using it like an array: scoops[0]
Try initializing scoops as an empty array. So something like this should work:
const [scoops, setScoops] = useState([]);
Also, where you are doing this: scoops[0].title you should instead use Optional chaining and use scoops[0]?.title

You should use Optional chaining (?.)
scoops?.[0]

Related

How to make child components use map() from App.js props data?

My react is 18.2. I want the child component to receive data and use map() from App.js.
I checked the child component received the data, but I don't know why does it can't use map().
It shows this error.
Uncaught TypeError: Cannot read properties of undefined (reading 'map')
So do anyone can fix it? I guess this is render problem, but I don't know how to fix it, thank you.
fetched():
[
{ id:1, date: '2011-01-01T00:00:00.000Z' },
{ id:2, date: '2013-09-03T00:00:00.000Z' },
{ id:3, date: '2012-04-02T00:00:00.000Z' },
{ id:4, date: '2013-12-08T00:00:00.000Z' },
{ id:5, date: '2010-01-23T00:00:00.000Z' },
];
App.js:
import { Child } from './Child';
const Test = () => {
const [toChild, setToChild] = useState()
useEffect(() => {
const data = async () => {
const fetchedData = await fetched();
setToChild(fetchedData)
};
data();
}, []);
const test = () => {
setToChild(fetchedData)
}
}
return(<Child toChild={toChild}>)
Child.js:
const Child = ({ toChild }) => {
const data = toChild;
const getDate = data.map((item) => item.date);
Since the data coming from your fetch is an array of objects, you may want to initialize the state as an empty array useState([]), useEffect will fire once the component has been mounted so at first, toChild will be undefined and that's why you are getting that error.
So basically:
import { useState, useEffect } from 'react'
import { Child } from './Child';
const Test = ()=>{
const [toChild,setToChild] = useState([])
useEffect(() => {
const data = async () => {
const fetchedData = await fetched();
setToChild(fetchedData)
};
data();
}, []);
return <Child toChild={toChild} />
Jsx is already rendered before javascript in react even though you fetch the data from server but html already rendered to the browser.so add any loading component to that. toChild?:<div>loading</div>:<Child toChild={toChild}/>

React hook setState and useContext is not working

I am trying to update a react context with an array
this is the cartcontext
import React, { useState } from "react";
import UniqueString from "../lib/unique-string";
import suspender from "../lib/suspender";
import db from '../db';
const uid = new UniqueString();
// set up the database when this function is called
// const CartDBContext = React.createContext(setUpDatabase())
const emptycart = {"454":{"test"}}
const CartContext = React.createContext([emptycart]);
// function to get all meals in the DB
async function getAllItemsFromDB() {
return new Promise((resolve, reject) => {
var allItems = db.table("cartitems").toArray().then((itemArry) => {
console.log("items in cart ",itemArry)
if (!itemArry || !itemArry.length) {
// array does not exist, is not an array, or is empty
// ⇒ do not attempt to process array
resolve([])
} else {
resolve(itemArry)
}
})
})
}
// using our suspender here
const resource = suspender(getAllItemsFromDB());
// The component itself
function CartContextProvider({ children }) {
// reading data from suspender
const items = resource.data.read() || [];
console.log("all items from suspender", items, resource)
// state to store list of items in the object Store
const [itemsList, setItemsList] = useState(items);
// if the itemList is null, umnount the component
// else return the appropriate JSX
return (
// pass the mealsList state and its setter as context values
<CartContext.Provider value={{ itemsList, setItemsList }}>
{children}
</CartContext.Provider>
);
}
export default CartContextProvider;
export { CartContext };
this is index.jsx where the context is used
import React, { useContext,useState,useEffect,useRef ,Suspense } from "react";
import CartContextProvider, { CartContext } from "../components/CartContextProvider";
function Index(){
...
const [itemsList, setItemsList] = useContext(CartContext);
useEffect(() => {
runIndexDb(data).then(async result => {
console.log("ItemList is ", itemsList)
console.log(result)
setItemsList(itemsList => [...itemsList, result]);
})
})
...
}
The print out of console.log
for itemsList
ItemList is {454: "test"}
result
0: {id: "00010164533955", name: "Hilsinger Company Sport Band - Black", productimg: "http://i5.walmartimages.com/asr/44e902de-30f8-40f3…9fdb.jpeg?odnHeight=180&odnWidth=180&odnBg=ffffff", unitofissue: "each", quantity: 2, …}
1: {id: "00014381104394", name: "A House Divided: Season 1 (DVD)", productimg: "http://i5.walmartimages.com/asr/99cfec5c-634e-4e26…4465.jpeg?odnHeight=180&odnWidth=180&odnBg=ffffff", unitofissue: "each", quantity: "1", …}
2: {id: "00016500590958", name: "One A Day Men's 50+ Mini Gels, Multivitamins for Men, 80 Ct", productimg: "http://i5.walmartimages.com/asr/7d44d419-bd6f-4808…b7ea.jpeg?odnHeight=180&odnWidth=180&odnBg=ffffff", unitofissue: "each", quantity: "1", …}
3: {id: "00022141041599", name: "Mepps Dressed Aglia Inline Spinner, Silver & Gray, 1/4 oz", productimg: "http://i5.walmartimages.com/asr/a0ce2579-300a-4536…0d63.jpeg?odnHeight=180&odnWidth=180&odnBg=ffffff", unitofissue: "each", quantity: "1", …}
the code is throwing an error at setItemsList
Unhandled Rejection (TypeError): setItemsList is not a function
I tried many different methods but no solution
what am I doing wrong and how can I updated the itemsList with the array from above?
useContext returns an object and not a list.
If you do it like so it should work.
Try to replace:
const [itemsList, setItemsList] = useContext(CartContext);
With:
const { itemsList, setItemsList } = useContext(CartContext);
Maybe you used useContext in a component that isn't wrapped by the context provider (using useContext in the same component with return context provider).

Objects are not valid as a React child (found: object with keys...)

I'm trying to set a state. Here is my code:
import React, { Component, useRef, Fragment, useEffect, useState } from "react";
import axios from "axios";
const Cart = (props) => {
const [useCart, setCart] = useState([]);
useEffect(() => {
(async () => {
const result = await axios
.get('https://example.com/sample')
.then((res) => {
setCart(res.data);
})
.catch((err) => {
console.error(err);
});
})();
}, []);
console.log(useCart);
return (
<div></div>
);
}
export default Cart;
The API returns value like that:
[
{
'id': 5,
'qwe': 'qwe',
'asd': [
{
'aaa': 'aaa',
'bbb': 'bbb'
}
],
'zxc': 'zxc'
},
{
'id': 7,
'qwe': 'qwe',
'asd': [
{
'aaa': 'aaa',
'bbb': 'bbb'
}
],
'zxc': 'zxc'
}
]
I'm not rendering this on component. I'm just trying to console log it. But it give error like that:
Error: Objects are not valid as a React child (found: object with keys {id, qwe, asd, zxc}). If you meant to render a collection of children, use an array instead.
I couldn't understand, where do I do wrong.
Don't know what's going wrong with your code but maybe I can help you debug it. I copied your API's response to a file and simply imported it inside the component to a variable. I then set the state to that variable and everything works fine, all console logs and everything. Check it out here-
https://codesandbox.io/s/angry-ramanujan-3k97v?file=/src/App.js
That tells me the problem could be coming from your useEffect where you are handling your API response.
One change that I made and I think you should do it too when you logout a state variable you should do it inside a lifecycle hook. So you should put your console.log(useCart); inside this
useEffect(()=>{ console.log(useCart); },[useCart])

Problem with using deeper nested data fetched from API

I encountered a problem when I try to fetch some data from PokeAPI. Here's my code for PokemonCard component.
import React, { useEffect, useState } from "react";
import axios from "axios";
const PokemonCard = ({ pokemonID }) => {
const [pokemon, setPokemon] = useState({});
useEffect(() => {
(async () => {
const result = await axios.get(
`http://pokeapi.co/api/v2/pokemon/${pokemonID + 1}`
);
setPokemon(result.data);
})();
// console.log(pokemon.weight)
}, [pokemonID]);
return (
<div className="pokemon">
{pokemon.sprites.front_default}
</div>
);
};
export default PokemonCard;
Everything works properly when I try to reach data like: pokemon.weight or pokemon.base_experience. But I get errors when I try to use some deeper nested variables.
pokemon.sprites.front_default gives me an error TypeError:
Cannot read property 'front_default' of undefined.
Here's a sample of data from API:
"name": "bulbasaur",
"order": 1,
"species": {
"name": "bulbasaur",
"url": "https://pokeapi.co/api/v2/pokemon-species/1/"
},
"sprites": {
"back_default": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/1.png",
"back_female": null,
"back_shiny": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/shiny/1.png",
"back_shiny_female": null,
"front_default": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png",
"front_female": null,
"front_shiny": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/1.png",
"front_shiny_female": null
},
"stats": [
{
"base_stat": 45,
"effort": 0,
"stat": {
"name": "hp",
"url": "https://pokeapi.co/api/v2/stat/1/"
}
}
],
"types": [
{
"slot": 2,
"type": {
"name": "poison",
"url": "https://pokeapi.co/api/v2/type/4/"
}
},
{
"slot": 1,
"type": {
"name": "grass",
"url": "https://pokeapi.co/api/v2/type/12/"
}
}
],
"weight": 69
PS. Is it a good practice to make about 150 separate calls to API in every child component? Or should I somehow do it with one call? Thank you.
You were trying to access a key inside an undefined key of pokemon variable. Please check the updated line where you are actually rendering.
{pokemon.sprites ? pokemon.sprites.front_default : ''}
As Pokemon is an empty object before the api fetches the data and
updates to the state, so pokemon.sprites is actually undefined.
import React, { useEffect, useState } from "react";
import axios from "axios";
const PokemonCard = ({ pokemonID }) => {
const [pokemon, setPokemon] = useState({});
useEffect(() => {
(async () => {
const result = await axios.get(
`http://pokeapi.co/api/v2/pokemon/${pokemonID + 1}`
);
setPokemon(result.data);
})();
// console.log(pokemon.weight)
}, [pokemonID]);
return (
<div className="pokemon">
//this should work for you
{pokemon.sprites ? pokemon.sprites.front_default : ''}
</div>
);
};
export default PokemonCard;
Gets much easier in Optional chaining (?.)
pokemon.sprites?.front_default

How to take a jest snapshot after axios fetched data in componentDidMount?

Component to test
class Carousel extends React.Component {
state = {
slides: null
}
componentDidMount = () => {
axios.get("https://s3.amazonaws.com/rainfo/slider/data.json").then(res => {
this.setState({ slides: res.data })
})
}
render() {
if (!slides) {
return null
}
return (
<div className="slick-carousel">
... markup trancated for bravity
</div>
)
}
}
export default Carousel
Test
import React from "react"
import renderer from "react-test-renderer"
import axios from "axios"
import Carousel from "./Carousel"
const slides = [
{
ID: "114",
REFERENCE_DATE: "2018-07-02",
...
},
{
ID: "112",
REFERENCE_DATE: "2018-07-06",
...
},
...
]
jest.mock("axios")
it("", () => {
axios.get.mockImplementationOnce(() => Promise.resolve({ data: slides }))
const tree = renderer.create(<Carousel />).toJSON()
expect(tree).toMatchSnapshot()
})
snapshot only records null, since at the moment of execution I suppose state.slides = null.
Can't put my finger on how to run expectations after axios done fetching the data.
Most of the samples online either use enzyme, or show tests with async functions that return promises. I couldn't find one that would show example only using jest and rendered component.
I tried making test function async, also using done callback, but no luck.
in short:
it("", async () => {
axios.get.mockImplementationOnce(() => Promise.resolve({ data: slides }))
const tree = renderer.create(<Carousel />);
await Promise.resolve();
expect(tree.toJSON()).toMatchSnapshot()
})
should do the job
in details: besides you have mocked call to API data is still coming in async way. So we need toMatchSnapshot call goes to end of microtasks' queue. setTimeout(..., 0) or setImmediate will work too but I've found await Promise.resolve() being better recognizable as "everything below is coming to end of queue"
[UPD] fixed snippet: .toJSON must be after awaiting, object it returns will never be updated
The accepted answer started to fail the next day. After some tweaking, this seems to be working:
import React from "react"
import renderer from "react-test-renderer"
import axios from "axios"
import Carousel from "./Carousel"
jest.mock("axios")
const slides = sampleApiResponse()
const mockedAxiosGet = new Promise(() => ({ data: slides }))
axios.get.mockImplementation(() => mockedAxiosGet)
// eventhough axios.get was mocked, data still comes anychrnonously,
// so during first pass state.slides will remain null
it("returns null initally", () => {
const tree = renderer.create(<Carousel />).toJSON()
expect(tree).toMatchSnapshot()
})
it("uses fetched data to render carousel", () => {
const tree = renderer.create(<Carousel />)
mockedAxiosGet.then(() => {
expect(tree.toJSON()).toMatchSnapshot()
})
})
function sampleApiResponse() {
return [
{
ID: "114",
REFERENCE_DATE: "2018-07-02",
...
},
{
ID: "114",
REFERENCE_DATE: "2018-07-02",
...
},
]
}

Categories