I am trying to fetch data from https://randomuser.me/api/ using react useEffect hook. Instead of the data to be displayed, I get an error, "Error: Objects are not valid as a React child (found: object with keys {number, name}). If you meant to render a collection of children, use an array instead". I logged the results on my console, I realised the problem is from some of the nested objects, which outputs [object Object]. However, I don't know why some other nested objects display correctly. Below is my code:
import { useState, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import '../../App.css';
const UsersList = () => {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
useEffect(() => {
fetch("https://randomuser.me/api/")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setItems(result.results);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, [])
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<div>
{items.map((item, id) => (
<div className="card content-panel" key={id}>
<img src={item.picture.medium} alt="Profile pic" className="avatar-user-list" />
<div className="card-text">
<p>
{item.name.first} {item.name.last}
</p>
<p>{item.location.street}</p>
<p>{item.email}</p>
</div>
<div className="arrow-bg">
<span className="arrow">→</span>
</div>
</div>
))}
</div>
);
}
}
export default UsersList;
item.location.street is an object.
According to the response API, this is the street object.
"street": {
"number": 4850,
"name": "Rue Dumenge"
},
Instead of rendering
<p>{item.location.street}</p>
You should render something like
<p>{`${item.location?.street?.number} ${item.location?.street?.name}`}</p>
Related
I am working on react website.
I have created one custom data fetching hook 'usePostFetch' as follows:
import React, { useState, useEffect } from "react";
//axios
import axios from "axios";
const usePostFetch = () => {
const [postData, setPostData] = useState([]);
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const getData = async () => {
setIsLoading(true);
try {
const res = await axios.get("http://localhost:8000/Sell");
const data = await res.data;
setPostData(data);
setIsLoading(false);
} catch (error) {
console.log("Error from fetch: " + error);
setError(error.message);
setIsLoading(false);
}
};
getData();
}, []);
const values = [
...new Set(
postData.map((post) => {
return post.productType;
})
),
];
return { postData, values, error, isLoading };
};
export default usePostFetch;
I have a product page that renders when I click any of the links on the home page with a link "/product/:productId".productId is the id of clicked link product.
Product Page:
import React, { useEffect, useState } from "react";
//react router dom
import { useParams } from "react-router";
//Hooks
import usePostFetch from "../../Hooks/usePostFetch";
//styles
import { Wrapper, Info, Discription } from "./Product.styles";
//Server
const Server = "http://localhost:8000";
const Product = () => {
const { productId } = useParams();
const { postData, isLoading, error } = usePostFetch();
const [data, setData] = useState({});
console.log(postData, isLoading, error);
useEffect( () => {
const fetchData = async () => {
var value = await postData.filter((post) => {
return post._id === productId;
});
console.log(value);
setData(value);
};
fetchData();
}, [postData]);
return (
<Wrapper>
<Info>
{isLoading && <h1> Loading.... </h1>}
{error && <p>ERROR </p>}
{console.log(data)}
<img
src={`${Server}/productImages/${data[0].productImage}`}
alt={`${data[0].productName}`}
/>
<div className="data">
<h1>{data[0].productName}</h1>
<h3>{data[0].productPrice}</h3>
</div>
</Info>
</Wrapper>
);
};
export default Product;
But when I go to that link I got data in console like this:
Because of these empty arrays, I got errors like this:
What can I do or what is wrong with my code?
It appears you are reading state that doesn't exist yet. The initial data state is an empty object:
const [data, setData] = useState({});
And on the initial render you are attempting to read from a 0 property, which OFC is undefined still.
data[0] --> OK, undefined
data[0].productName --> NOT OK, throws error trying to access from undefined
You can conditionally render the data content when you know it's populated:
<Wrapper>
<Info>
{isLoading && <h1> Loading.... </h1>}
{error && <p>ERROR </p>}
{console.log(data)}
{data[0] && (
<img
src={`${Server}/productImages/${data[0].productImage}`}
alt={`${data[0].productName}`}
/>
<div className="data">
<h1>{data[0].productName}</h1>
<h3>{data[0].productPrice}</h3>
</div>
)
</Info>
</Wrapper>
Or you can just use the Optional Chaining operator to defend against null/undefined property accesses:
<Wrapper>
<Info>
{isLoading && <h1> Loading.... </h1>}
{error && <p>ERROR </p>}
{console.log(data)}
<img
src={`${Server}/productImages/${data[0]?.productImage}`}
alt={`${data[0]?.productName}`}
/>
<div className="data">
<h1>{data[0]?.productName}</h1>
<h3>{data[0]?.productPrice}</h3>
</div>
</Info>
</Wrapper>
It also seems that you are really expecting data to be an array, so you will want your initial state to maintain a state/type invariant, so it should also be declared as an array.
const [data, setData] = useState([]);
I am learning React as I am fetching data from Pokéapi to make a list component, card component, detail component and filter component. I am trying to make a filter so you can filter by pokémon type. Only the cards that also contain that type string should then render (Not there yet). So I am not sure if a) I should make a different call from API inside PokemonList depending on selected value or b) if I should compare the values and just change how the PokemonCard element is rendered inside PokemonList.js depending on the comparison. I managed to pass data from filter to the list component. I have then been trying to pass the type data from PokemonCard.js to the list component so that I can compare these two values but I find it hard to use callbacks to pass the type data from the card component, since I dont pass it through an event or something like that.
Which method should I use here to simplify the filtering? Make different API call or render PokemonCard element conditionally?
Is it a good idea to compare filter option to pokemon card's type in PokemonList.js? Then how can I pass that data from the card component since I don't pass it through click event?
Thankful for any ideas! I paste the code from list component that contains the cards, card component and filter component.
PokemonList component:
import { useState } from 'react';
import useSWR from 'swr';
import PokemonCard from './PokemonCard';
import PokemonFilter from './PokemonFilter';
import './PokemonList.css';
const PokemonList = () => {
const [index, setIndex] = useState(0);
const [type, setType] = useState('');
function selectedType(type) { // value from filter dropdown
setType(type)
console.log("handled")
console.log(type)
}
const url = `https://pokeapi.co/api/v2/pokemon?limit=9&offset=${index}`;
const fetcher = (...args) => fetch(...args).then((res) => res.json())
const { data: result, error } = useSWR(url, fetcher);
if (error) return <div>failed to load</div>
if (!result) return <div>loading...</div>
result.results.sort((a, b) => a.name < b.name ? -1 : 1);
return (
<section>
<PokemonFilter onSelectedType={selectedType} selectedPokemonType={type} />
<div className="pokemon-list">
<div className="pokemons">
{result.results.map((pokemon) => (
<PokemonCard key={pokemon.name} pokemon={pokemon} /> // callback needed??
))}
</div>
<div className="pagination">
<button
onClick={() => setIndex(index - 9)}
disabled={result.previous === null}
>
Previous
</button>
<button
onClick={() => setIndex(index + 9)}
disabled={result.next === null}
>
Next
</button>
</div>
</div>
</section>
)
}
export default PokemonList;
PokemonCard component:
import { Link } from "react-router-dom";
import useSWR from 'swr';
import './PokemonCard.css';
const PokemonCard = ({ pokemon }) => {
const { name } = pokemon;
const url = `https://pokeapi.co/api/v2/pokemon/${name}`;
const { data, error } = useSWR(url);
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
const { types, abilities } = data;
// types[0].type.name <---- value I want to pass to PokemonList.js
return (
<div className='pokemon-card'>
<div className='pokemon-card__content'>
<img
className='pokemon-card__image'
src={data.sprites.front_default}
alt={name}
/>
<div className='pokemon-card__info'>
<p className='pokemon-card__name'>Name: {name}</p>
<p className='pokemon-card__abilities'>Abilities: {abilities[0].ability.name}</p>
<p className='pokemon-card__categories'>Category: {types[0].type.name}</p>
</div>
</div>
<Link className='pokemon-card__link' to={{
pathname: `/${name}`,
state: data
}}>
View Details
</Link>
</div>
)
}
export default PokemonCard;
PokemonFilter component:
import './PokemonFilter.css';
import useSWR from 'swr';
const PokemonFilter = ({onSelectedType, selectedPokemonType}) => {
const url = `https://pokeapi.co/api/v2/type/`;
const fetcher = (...args) => fetch(...args).then((res) => res.json())
const { data: result, error } = useSWR(url, fetcher);
if (error) return <div>failed to load</div>
if (!result) return <div>loading...</div>
function filteredTypeHandler(e) {
console.log(e.target.value);
onSelectedType(e.target.value);
}
console.log(selectedPokemonType)
return(
<div className="pokemon-types__sidebar">
<h2>Filter Pokémon by type</h2>
<select
name="pokemon-type"
className="pokemon-types__filter"
onChange={filteredTypeHandler}
>
<option value="All">Filter By Type</option>
{result.results.map((type) => {
return (
<option key={type.name} value={type.name}> {type.name}</option>
)
})}
</select>
</div>
)
}
export default PokemonFilter;
Here is an example to improve, modify, ... I didn't test, it's just a visual example.
I don't know about useSWR sorry, I use axios in my example...
If you want to centralize all your API requests, you can create a useApi hook, on the internet you will find tutorials.
PokemonList.js
import React, { useState, useEffect } from 'react';
import axios from 'axios'; // or swr
import PokemonFilter from './PokemonFilter';
import PokemonCard from './PokemonCard';
export default function PokemonList() {
const [data, setData] = useState([]);
const [filter, setFilter] = useState('');
// Executed every first render
useEffect(() => {
getData();
}, []);
// Executed only when filter changes
useEffect(() => {
getDataByTypes(filter);
}, [filter]);
// Get data
const getData = async () => {
const uri = 'https://xxx';
try {
const response = await axios.get(uri);
setData(response.data...);
} catch (error) {
console.log(error);
}
};
// Get data by types
const getDataByTypes = async (filter) => {
const uri = `https://xxx/type/${filter}...`;
if (filter) {
try {
const response = await axios.get(uri);
setData(response.data...);
} catch (error) {
console.log(error);
}
}
};
return (
<div className="main">
<PokemonFilter filter={filter} setFilter={setFilter} />
<div className="container">
<div className="cards-container">
{data.map((d) => (
<PokemonCard key={d.name} data={d} />
))}
</div>
</div>
</div>
);
}
PokemonCard.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default function PokemonCard({ data }) {
const [pokemons, setPokemons] = useState();
useEffect(() => {
getPokemons(data);
}, [data]);
// Get Pokemons
const getPokemons = async (data) => {
const uri = `https://xxx/pokemon/${data.name}/`;
try {
const response = await axios.get(uri);
setPokemons(response.data...);
} catch (error) {
console.log(error);
}
};
return (
<div>
{pokemons && (
<div className="card">
<img src={pokemons.sprites.front_default} alt={pokemons.name} />
<p>{pokemons.name}</p>
<p>{pokemons.abilities[0].ability.name}</p>
<p>{pokemons.types[0].type.name}</p>
</div>
)}
</div>
);
}
PokemonFilter.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default function PokemonFilter({ filter, setFilter }) {
const [types, setTypes] = useState([]);
useEffect(() => {
getType();
}, []);
// Get Type
const getType = async () => {
const uri = 'https://xxx/type/';
try {
const response = await axios.get(uri);
setTypes(response.data.results....);
} catch (error) {
console.log(error);
}
};
const handleFilter = (e) => {
setFilter(e.target.value);
};
return (
<select onChange={handleFilter} value={filter}>
<option>Filter by type</option>
{types.map((type) => {
return (
<option key={type.name} value={type.name}>
{type.name}
</option>
);
})}
</select>
);
}
I am getting the following 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.
I am using context in react and having a problem with props as I have forEach loop in inner components to fetch data from the database.
I don't know how to render a collection by using an array here.
Here is the code to fetch data due to which the error is occurring:
import React, { useEffect, useState } from "react";
import { db } from "../services/firebase";
import style from "./css/dashboard.module.css";
import InputTask from "./InputTask";
import Task from "./Task";
const Tasks = ({ user }) => {
console.log("Tasks");
return (
<>
<div className={style.card}>
<InputTask user={user} />
</div>
{true
? db
.collection("Users")
.doc(`${user.email}`)
.collection("tasks")
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(doc.id, " => ", doc.data());
return (
<div className={style.card}>
<Task todo={doc} />
</div>
);
});
})
: null}
</>
);
};
export default Tasks;
Error's snapshot attached here
Your render method (part attached below) will not wait until .then is triggered. Instead, it will return the Promise as is. Thus the Error: Objects are not valid as a React child (found: [object Promise]) you mentioned.
{
true
? db
.collection("Users")
.doc(`${user.email}`)
.collection("tasks")
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(doc.id, " => ", doc.data());
return (
<div className={style.card}>
<Task todo={doc} />
</div>
);
});
})
: null
}
As this is an async operation, it's better if you use a state for this part. For instance, you can fetch the data when your component mounts (with a useEffect), put everything into a state (with useState), and render the state.
import React, { useEffect, useState } from "react";
import { db } from "../services/firebase";
import style from "./css/dashboard.module.css";
import InputTask from "./InputTask";
import Task from "./Task";
const Tasks = ({ user }) => {
const [tasks, setTasks] = useState([]);
// fetching data again when user changes
useEffect(() => {
db
.collection("Users")
.doc(`${user.email}`)
.collection("tasks")
.get()
.then((querySnapshot) => {
setTasks(querySnapshot);
})
}, [user]);
return (
<>
<div className={style.card}>
<InputTask user={user} />
</div>
{tasks.length
? tasks.map((task) =>
<div className={style.card}>
<Task todo={task} />
</div>
)
: null}
</>
);
};
export default Tasks;
You haven't imported useContext hook from react
Try this
import React, { useEffect, useState, useContext } from "react";
import { auth } from "../services/firebase";
export const AuthContext = React.createContext();
export const AuthProvider = (props) => {
const [loading, setLoading] = useState(true);
const [currentUser, setCurrentUser] = useState(null);
useEffect(() => {
auth.onAuthStateChanged((user) => {
setCurrentUser(user);
setLoading(false);
});
}, []);
if (loading) {
return <p>Loading...</p>;
}
// console.log(children);
return (
<AuthContext.Provider value={{ currentUser }}>
{props.children}
</AuthContext.Provider>
);
};
I want to render a list from a JSON URL. However, I have the following error: Objects are not valid as a React child (found: TypeError: Failed to fetch). If you meant to render a collection of children, use an array instead. What am I going wrong? Thanks for your answer
//hooks.tsx
import { useEffect, useState } from 'react'
export const useFetch = () => {
const [data, setData] = useState([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
useEffect(() => {
setLoading(true)
setError(null)
fetch('https://jsonkeeper.com/b/Z51B')
.then(res => res.json())
.then(json => {
setLoading(false)
if (json.data) {
setData(json.data)
} else {
setData([])
}
})
.catch(err => {
setError(err)
setLoading(false)
})
}, [])
return { data, loading, error }
}
//index.tsx
import React from 'react';
import { useFetch } from "./hooks.js";
import {CardItem} from './card';
export const List = () => {
const { data, loading, error } = useFetch()
if (loading) return <div>Loading...</div>
if (error) return <div>{error}</div>
return (
<>
<ul>
{data.map((item: any, index: any) => (
<li key={index}>
{item.names.map((name: any) => {
return <CardItem
family={item.family}
name={name}
/>
})
}
</li>
))}
</ul>
</>
);
};
I guess the problem is with your setError(err) in catch block of your custom hook. It should be setError(err.message).
I fetch some JSON, add it to state (becoming an array of objects) and then try to console log the state. I'm able to successfully console log the array and an object in the array, but when I try to log a property of one of those objects it throws an error. Why is this?
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
function App() {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
useEffect(() => {
fetch(
"https://my-json-server.typicode.com/typicode/demo/db" //test json data
)
.then((res) => res.json())
.then(
(result) => {
setIsLoaded(true);
setItems(result.posts);
},
(error) => {
setIsLoaded(true);
setError(error);
}
);
}, []);
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
console.log(items); //works
console.log(items[1]); //works
//console.log(items[1].id); //Cannot read property 'id' of undefined
return <div></div>;
}
}
ReactDOM.render(
<React.StrictMode>
<h1> Test </h1>
<App />
</React.StrictMode>,
document.getElementById("root")
);
When successfully receiving the result, swapping the order of setIsLoaded(true) and setItems(result.posts) has solved my problem. Still am curious as to why the order of the setStates caused the error.