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.
Related
I'm getting this error message TypeError: Cannot read properties of null (reading 'useState') when I use my custom hooks inside the getStaticProps to fetch the data from the firebase firestore. Anyone, please help me with this?
Challenges page code:
import Card from "../components/reusable/Card"
import { useCollection } from "../hooks/useCollection"
const Challenges = ({ challenges }) => {
return (
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 justify-items-center mt-8">
{challenges.map((challenge) => {
return (
<Card key={challenge.id} card={challenge} />
)
})}
</div>
)
}
export default Challenges
export async function getStaticProps() {
const { documents, isLoading } = useCollection("challenges", null, null, [
"createdAt",
"desc",
])
return {
props: {
challenges: documents,
},
}
}
useCollection hook code:
import { useEffect, useState } from "react"
import { collection, limit, onSnapshot, orderBy, query, where } from "firebase/firestore"
import { db } from "../firebase/config"
export const useCollection = (c, q, l, o) => {
const [documents, setDocuments] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
let ref = collection(db, c)
if (q) {
ref = query(ref, where(...q))
}
if (o) {
ref = query(ref, orderBy(...o))
}
if (l) {
ref = query(ref, limit(l))
}
const unsubscribe = onSnapshot(ref, (snapshot) => {
const results = []
snapshot.docs.forEach(
(doc) => {
results.push({ ...doc.data(), id: doc.id })
},
(error) => {
console.log(error)
setError("could not fetch the data")
}
)
// update state
setDocuments(results)
setIsLoading(false)
setError(null)
})
return () => unsubscribe()
}, [])
return { documents, error, isLoading }
}
You can use useState or any hook only inside a React Component, or a custom hook as Rules of Hooks says, and getStaticProps is none of the two. Plus, it only runs at build time, so not sent to the browser:
If you export a function called getStaticProps (Static Site Generation) from a page, Next.js will pre-render this page at build time using the props returned by getStaticProps.
You could either move the data fetching to the client and get rid of getStaticPros, like so:
import Card from "../components/reusable/Card";
import { useCollection } from "../hooks/useCollection";
const Challenges = () => {
const {
documents: challenges,
isLoading,
error,
} = useCollection("challenges", null, null, ["createdAt", "desc"]);
if (isLoading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error happend</p>;
}
return (
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 justify-items-center mt-8">
{challenges.map((challenge) => {
return <Card key={challenge.id} card={challenge} />;
})}
</div>
);
};
export default Challenges;
Or transform useCollection to a normal async function without any hook in it, and use it inside getStaticProps. But it doesn't seem like it's what you want, since you are creating a subscription on the client and all.
I'm trying to add functionality to fetch the next 4 comments but when I click on the Load more comments button, it throws this error message.
Uncaught FirebaseError: Invalid query. You must not call startAt() or startAfter() before calling orderBy().
Anyone, please help me with this.
SolutionComments.js
import React, { useState } from "react"
import { useParams } from "react-router-dom"
import { useCollection } from "../../hooks/useCollection"
import Comment from "./Comment"
import CommentForm from "./CommentForm"
const SolutionComments = () => {
const [activeComment, setActiveComment] = useState(null)
const { id } = useParams()
const { documents, fetchMore } = useCollection(`solutions/${id}/comments`, null, 4, [
"createdAt",
"desc",
])
const fetchMoreComments = () => {
fetchMore(documents[documents.length - 1])
}
return (
<div className="mt-10">
<CommentForm docID={id} />
<div>
{documents &&
documents.map((comment) => (
<Comment
key={comment.id}
comment={comment}
replies={comment.replies}
activeComment={activeComment}
setActiveComment={setActiveComment}
/>
))}
</div>
<button onClick={fetchMoreComments} className="text-white bg-purple-500">
Load More Comments!
</button>
</div>
)
}
export default SolutionComments
useCollection hook:
import { useEffect, useRef, useState } from "react"
// firebase import
import {
collection,
limit,
onSnapshot,
orderBy,
query,
startAfter,
where,
} from "firebase/firestore"
import { db } from "../firebase/config"
export const useCollection = (c, _q, _l, _o) => {
const [documents, setDocuments] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
// if we don't use a ref --> infinite loop in useEffect
// _query is an array and is "different" on every function call
const q = useRef(_q).current
const o = useRef(_o).current
let ref = collection(db, c)
useEffect(() => {
if (q) {
ref = query(ref, where(...q))
}
if (o) {
ref = query(ref, orderBy(...o))
}
if (_l) {
ref = query(ref, limit(_l))
}
const unsubscribe = onSnapshot(ref, (snapshot) => {
const results = []
snapshot.docs.forEach(
(doc) => {
results.push({ ...doc.data(), id: doc.id })
},
(error) => {
console.log(error)
setError("could not fetch the data")
}
)
// update state
setDocuments(results)
setIsLoading(false)
setError(null)
})
// unsubscribe on unmount
return unsubscribe
}, [c, q, _l, o, isLoading])
// I'm using this function to fetch next 4 comments including the previous ones.
const fetchMore = (doc) => {
ref = query(ref, orderBy(...o), startAfter(doc), limit(_l))
onSnapshot(ref, (snapshot) => {
const results = []
snapshot.docs.forEach(
(doc) => {
results.push({ ...doc.data(), id: doc.id })
},
(error) => {
console.log(error)
setError("could not fetch the data")
}
)
// update state
console.log(results)
setDocuments([...documents, results])
setIsLoading(false)
setError(null)
})
}
return { documents, fetchMore, error, isLoading }
}
You are using onSnapshot() to start your query. Since you are doing paging, it is likely you want to use getDocs() instead.
At the very least, you want to be sure to shut down your previous listener for onSnapshot() before starting a new one. But it is rarely all that useful/correct to use an onSnapshot() with paging. Things get hairy-complicated under that approach.
I have a parent component called App. I want to send the data that i took from the api(it includes random questions and answers) to child component.In child componenet(QuestionGrid), when i want to take the first question inside the array that come from api, I face the error. i want to use console.log(items[0].question) to see the first question but it fires error.But when I use console.log(items) it allow me to see them. I also aware of taking the data after they loaded.I used also useEffect. Here is my parent component
import './App.css';
import React, { useState,useEffect} from 'react';
import QuestionGrid from './components/QuestionGrid';
function App() {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
useEffect(() => {
fetch("https://opentdb.com/api.php?amount=40&category=9&difficulty=medium&type=multiple")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setItems(result.results);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, [])
return (
<div className="App">
<QuestionGrid isLoaded={isLoaded} items={items}/>
</div>
);
}
export default App;
Here is my child component
import React, { useState, useEffect } from 'react';
export default function QuestionGrid({ isLoaded, items }) {
if(isLoaded){
console.log(items[0].question)
}
return isLoaded ?
<section className="cards">
</section> : <h1>Loading</h1>;
}
It will fire and error because the initial state of items is an empty array. And there is no indexes and object on the items state on the first render.
you can check if the the items is loaded by only checking its length.
return items.length > 0 ? <h1>your jsx component</h1> : <span>Loading...</span>
First thing, you should use the .catch() in fetch like:
fetch("https://opentdb.com/api.php?amount=40&category=9&difficulty=medium&type=multiple")
.then(res => res.json())
.then((result) => {
setIsLoaded(true);
setItems(result.results);
})
.catch(error => {
setIsLoaded(true);
setError(error);
)}
)
You are checking for isLoaded but not if there is any data. You are setting isLoaded(true) in both your result and also in error (which is not bad).
The error is caused because there is nothing in items[0]. To check for this you can call console.log(items?.[0].question) or you can make the check in your if-condition if(items.length > 0)
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>
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).