How to fix Error: object is not valid as React child - javascript

I am learning React and I was trying to develop a code to fetch random 10 numbers with corresponding interesting facts from an api "Numbers API".
the issue I am facing is that, when I run the code, an error appears
"Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead"
below I have attached the code:
const originalArray = new Array(10).fill(0);
const mappedArray = originalArray.map((n, index) =>
Math.floor(Math.random() * 100)
);
console.log("mappedArray", mappedArray);
console.log("joined array string", mappedArray.join(","));
async function callApi(numbers) {
const fetchResponse = await fetch(`http://numbersapi.com/${numbers}/math`);
if (fetchResponse.ok) {
const data = await fetchResponse.json();
console.log("all good, heres the response", data);
} else console.log("There was a problem");
}
const Number = async() => {
const [add, setAdd] = React.useState([]); // <-- valid initial empty state
React.useEffect(() => {
callApi(mappedArray).
then(values => setAdd(values));
}, []); // <-- invoke on component mount
<div className="container">
{add.map((newlist, i) => (
<div className="item" key={i}>
{newlist}
</div>
))}
</div>
);
export default Number;
Can someone help me fix this issue? Thank you

You need to maintain state. React is very particular with it's lifecycle. So here I have a data state, and a function - setData - that updates it.
For React function components we use useEffect - essentially the old componentDidMount from class components - to load data when the component first mounts. We can then set the state with that data which we now know is an array of objects.
Once the state updates the component gets re-rendered. You can then iterate over the Object.entries using the key for the key, and the value as the text source.
function Number() {
// Initialise the state with an empty array
const [data, setData] = useState([]);
// Separate out the function that creates the number array
function createNumbers() {
const originalArray = new Array(10).fill(0);
return originalArray.map(() => Math.floor(Math.random() * 100)).join('');
}
useEffect(() => {
async function getNumbers() {
try {
const res = await fetch(`http://numbersapi.com/${createNumbers()}/math`);
const data = await res.json();
// Set the new component state using the data
setData(data);
} catch (err) {
console.log(err);
}
}
getNumbers();
}, []);
return (
<div className="container">
{Object.entries(data).map(([key, value]) => (
<div className="item" key={key}>
{value}
</div>
))}
</div>
);
};
export default Number;

callApi is async so it implicitly returns a Promise, which you save into add and attempt to render. This is the object that React is complaining about.
You will want to invoke this function within the React component lifecycle and save the result into state to be rendered.
const Number = () => {
const [add, setAdd] = React.useState([]); // <-- valid initial empty state
React.useEffect(() => {
callApi(mappedArray).
then(values => setAdd(values));
}, []); // <-- invoke on component mount
return (
<div className="container">
{add.map((newlist, i) => (
<div className="item" key={i}>
{newlist}
</div>
))}
</div>
);
};
When you send a comma separated list of numbers to this numbersapi it returns a JSON object that isn't renderable as an array.
The response format will always be a JSON map from numbers to facts,
of at most 100 numbers.
Example response to http://numbersapi.com/53,65,70/math
{
"53": "53 is the 16thprime number.",
"65": "65 is the 23rdsemiprime and the 3rd of the form (5.q)it is an octagonal number.",
"70": "70 is the smallest weird number."
}
It seems you want to either save the values into state:
React.useEffect(() => {
callApi(mappedArray).
then(result => setAdd(Object.values(result)));
}, []);
Or you can get the array of values when rendering:
return (
<div className="container">
{Object.values(add).map((newlist, i) => (
<div className="item" key={i}>
{newlist}
</div>
))}
</div>
);
If you want both the key and the value, then use Object.entries to get an array of array of key value pairs.
Example:
[
['53', '53 is the 16thprime number.'],
['65', '65 is the 23rdsemiprime and the 3rd of the form (5.q)it is an octagonal number.'],
['70', '70 is the smallest weird number.'],
]
return (
<div className="container">
{Object.entries(add).map(([key, value], i) => (
<div className="item" key={key}>
{key}: {value}
</div>
))}
</div>
);

That's because your add function is still return a Promise since it an async function
You will still need to await for add to finished, then use it:
Something like this:
const Number = async () => {
const data = await add;
<div className="container">
{data.map((newlist, i) => (
<div className="item" key={i}>
{newlist}
</div>
))}
</div>;
};

Related

How do you catch if an incoming object (from a http request) doesn't have a certain array? (React JS)

I'm fetching an object (with a text value and a few arrays) from an API and transferring those to local variables for use. All is working except for when that object I'm fetching doesn't have one of those arrays and I try to use it the whole site crashes. I'm lost on how to do the error handling here.
import React, { useEffect, useState } from 'react'
import classes from './Streaming.module.css'
const Streaming = (props) => {
const [streamingOn, setStreamingOn] = useState(false)
const [streamingData, setStreamingData] = useState(null)
async function receiveStreaming() {
await fetch(`https://api.themoviedb.org/3/movie/${props.movie}/watch/providers?
api_key=35135143f12a5c114d5d09d17dfcea12`)
.then(res => res.json())
.then(result => {
setStreamingData(result.results.US)
setStreamingOn(true)
}, (error) => {
console.error("Error: ", error)
}
)
// console.log(data)
}
const displayStreaming = streamingData => {
let sortedData = { ...streamingData }
let streamData = sortedData.flatrate
let rentData = sortedData.rent
let linkText = streamingData.link
let id = Math.random()
let streamListItems = streamData.map((movie) =>
<li key={id}>
<a href={linkText}><img className={classes.logoimg} src=. {'https://image.tmdb.org/t/p/w500/' + movie.logo_path}></img></a>
</li>)
let rentListItems = rentData.map((movie) =>
<li key={id}>
<a href={linkText}><img className={classes.logoimg} src={'https://image.tmdb.org/t/p/w500/' + movie.logo_path}></img></a>
</li>)
return (
<React.Fragment>
<p>Stream on</p>
<ul className={classes.logolist}>{streamListItems}</ul>
<p>Rent on</p>
<ul className={classes.logolist}>{rentListItems}</ul>
</React.Fragment>
)
// console.log(sortedData)
}
return (
<React.Fragment>
<button onClick={receiveStreaming}></button>
{<div className={classes.streaminglogos}>
{(streamingOn) && <div>{displayStreaming(streamingData)}</div> }
</div>}
</React.Fragment>
)
}
export default Streaming
Use optional chaining to check the expected array has been received or not.
Assuming that you need to show an error UI when the expected array was not received, then you can set a flag(isErrored) to true and render that conditionally.
Handling Response JSON
if (!result?.results?.US) {
setIsErrored(true);
} else {
setStreamingData(result.results.US)
setStreamingOn(true);
}
Rendering Error UI conditionally
{isErrored && (<ErrorUI />)}
There are a few things you can do here. The first is that you could check if the array exists when you first get it and then append it on to it if it doesn't.
Maybe something like:
if(!result.results.US){
result.results.US = []
}
Or you could check if the array exists when you are displaying the data by conditionally rendering the component (or piece of component). If the data does not have the array (using the above method) don't display it.
Hope this helps!

How to read array document field from firebase firestore (React js)

Hi I am trying to get todo array field from cloud firestore database in a react project. When i print out with console log I can see the empty array symbol but It throws error at the same time and does not render the page.
This is the Firestore.js file to get data from firebase.
export const userTodoList = async (email) => {
await onSnapshot(doc(db, "users", email), (doc) => {
console.log(doc.data().todos);
return doc.data().todos;
});
};
This the MainPage.js file to get the array and pass to CardList component.
const MainPage = () => {
const todoArray = userTodoList(auth.currentUser.email);
const [todos, setTodos] = useState(todoArray);
return (
<div>
<section>
<NavBar></NavBar>
<ToDoForm />
</section>
<section>
<CardList array={todos} />
</section>
</div>
);
};
This is the CardList component to render the cards with the array mapping. I am checking for if the array length is 0 or not but still get errors.
const CardList = (props) => {
const array = props.array;
if (array.length === 0) {
return (
<div className="grid_container">
<h2>Found no todos</h2>
</div>
);
}
return (
<div className="grid_container">
{array.map((todo) => (
<Card title={todo.title} />
))}
</div>
);
};
Errors.
The object.map is not a function error occurs because the map method is not implemented on objects. To iterate over an object, use the Object.keys() method to get an array of the object's keys and call the map()method on the array of keys.
I.e You can use Object.keys() or Object.entries() and then use map
For example:
array.keys(obj).map(key => {
//other statements
});
For more details you can check this article

Why can't I map this array

Can you help me figure out why I am not able to map this array. Here below are the error and the codes I am running:
TypeError: posts.map is not a function
and here is my codes causing the above error:
import React from 'react';
import {useEffect, useState} from 'react';
import { Container, Row, Col } from 'bootstrap-4-react';
export default function Post() {
//posts array to be mapped
const [posts, setPosts] = useState([{
title: "",
postContent: ""
}]);
useEffect(() => {
//fetches a GET route
fetch(`${process.env.REACT_APP_SERVER_URL}/posts/:id`).then(res => {
if (res.ok) {
return res.json()
}
}).then(jsonRes => setPosts(jsonRes));
})
return (
<div>
<h1>Hello</h1>
//cant seem to be able to map this array
{posts.map(post => {
<>
<h1>{post.title}</h1>
<p>{post.postContent}</p>
</>
})}
</div>
)}
You need to wrap the mapped returned code block within parenthesis ()
and not in curly brackets {} in order to return the html correctly
//...
return (
<div>
<h1>Hello</h1>
{posts.map(post => (
<>
<h1>{post.title}</h1>
<p>{post.postContent}</p>
</>
))}
</div>
)
}
Edit:
Also, I suggest adding an empty dependency array as the second argument for your useEffect(() => { //your code }, [])
This will make it so your component doesn't re-render sporadically and end up fetching your data a ridiculous amount of times.
This is maybe because the response is not an array. Try to console.log the response. You can also change its type by using Array.isArray(jsonRes). The second problem is you are not returning the individual element inside the map function.
{posts.map((post) => (
<>
<h1>{post.title}</h1>
<p>{post.postContent}</p>
</>
))}
Your useEffect also don't have any dependencies, this will result in fetching data on every render. So you must use an empty array as the second argument inside useEffect to tell it to execute only once.
useEffect(() => {
//fetches a GET route
fetch(`${process.env.REACT_APP_SERVER_URL}/posts/:id`).then(res => {
if (res.ok) {
return res.json()
}
}).then(jsonRes => setPosts(jsonRes));
}, [])

How to pass hook value from parent to child in react.js

So im working on an inventory app, converting all my class components to functional components.. but when i try to pass the inventory value to the child element, it gives me an error of can't set .map on undefined
this is my app component
const App = () => {
const [inventory, setInventory] = useState([]);
const [pointer, setPointer] = useState('')
const addProduct = (item) => {
if(inventory.some(product => product.name === item.name)){
setInventory(
inventory.map(product => {
if(product.name === item.name){
product.quantity += parseInt(item.quantity);
return product;
} return product;
})
)
return;
}
const newItem = {
id: uuid(),
name: item.name,
quantity: parseInt(item.quantity),
unit: item.unit
}
setInventory(
...inventory, newItem
)
}
const updateQuantity = (item)=> {
// this.Modal.current.toggleModal();
setPointer(item.id)
}
const confirmUpdate = (quantity, pointer) => {
setInventory(inventory.map(item => {
if(item.id === pointer){
item.quantity = quantity;
return item;
}
return item;
})
)
}
const deleteItem = (id) => {
setInventory(
inventory.filter(item => item.id !== id)
)
}
return (
<div className="App">
<Header />
<div className="container">
<h1 style={{ width: '100%' }}>Inventory</h1>
<AddItem addProduct={addProduct}/>
<Inventory updateQuantity={updateQuantity} deleteItem={deleteItem} inventory={inventory}> </Inventory>
</div>
<UpdateModal confirmUpdate={confirmUpdate} pointer={pointer}/>
</div>
)
}
child component
const Inventory = props => {
return (props.inventory.map(item => (
<Item
key={item.id}
updateQuantity={props.updateQuantity}
deleteItem={props.deleteItem}
item={item}
/>)))
}
All I want is to pass the inventory value in the app component to the inventory component to map it... but I get the following error
TypeError: props.inventory.map is not a function
I'm sure the answer is simple but I'm stuck in a google wormhole and I can't find the answer...
UPDATE...
The attribute is sent as an object not an array for some reason...
console.log(typeof props.inventory) always returns an object no matter what I do...
I tried a couple of methods...
1-Spreading it out as an array inside the attribute value, [...inventory], raises another error
2- Declaring as a new Array() inside the useState hook, still nothing
3- using Array.from(inventory) inside the attribute call, still nothing..
I am new to react so there must be something I'm missing
You are converting the array to Object here:
setInventory({
...inventory, newItem
})
It must be:
setInventory([
...inventory, newItem
])
So here's what I did wrong...
My hook updating function had a wrong syntax but it was uncaught by react, because apparently the attribute is always passed as an object regardless? I'm not sure..
anyways restructuring my hook function fixed it...
instead of
setInventory(
...inventory, newItem
)
it was
setInventory(inventory =>
[...inventory, newItem]
)
yeah, that solved it..

Can't access array data from API call when using map() in React

I'm attempting to map over data I received from an API call. Getting shallow endpoints works fine, but anything nested gives me an error.
The goal is to get all of the opening themes and display them in a 'ul'.
The exact error "TypeError: anime.opening_themes is undefined"
Repo to the project
Heres the endpoints.
Heres my component.
const AnimeDetails = (props) => {
const API = 'https://api.jikan.moe/v3/anime'
const initialState = {
anime: []
}
const [anime, setAnime] = useState(initialState)
useEffect(() => {
const getAnime = async () => {
const response = await fetch(`${API}/${props.match.params.animeId}`)
const data = await response.json()
console.log(data);
setAnime(data) // set initial state to hold data from our API call
}
getAnime()
}, []) // [] prevents useEffect from running in an infinite loop
return (
<AnimeDetailsWrapper>
<Title>{anime.title}</Title>
<Details>
{anime.opening_themes
.map((song, index) => (
<li key={index}>{song}</li>
))}
</Details>
</AnimeDetailsWrapper>
)
}
Your initial state is an empty array, not an empty object:
const initialState = {
anime: []
}
When your component mounts, there is no data yet, so you're attempting to render [].opening_themes.map, and obviously there is no opening_themes property on an empty array.
Set your initial state to an empty object instead:
const initialState = {}
And you will probably want to test that you have data before attempting to render it:
return anime.mal_id && (
<AnimeDetailsWrapper>
<Title>{anime.title}</Title>
<Details>
{anime.opening_themes
.map((song, index) => (
<li key={index}>{song}</li>
))}
</Details>
</AnimeDetailsWrapper>
)
This will prevent your component from rendering anything until your anime state contains a mal_id property.
The first time you render your component, the state anime is equal to {anime: []}, which has no property called opening_themes.
you should try like this
first, remove initialState code and use direct code like following
if the response is in the form of an array
const [anime, setAnime] = useState([])
if the response is in the form of an object
const [anime, setAnime] = useState({})
otherwise, null will work with any response
const [anime, setAnime] = useState(null)
return code like this
return (
<> {anime && <AnimeDetailsWrapper>
<Title>{anime.title}</Title>
<Details>
{anime.opening_themes
.map((song, index) => (
<li key={index}>{song}</li>
))}
</Details>
</AnimeDetailsWrapper>}</>
)

Categories