I'm trying to build a loading component so that my app doesn't try and display my API data before it has loaded, causing a fatal error. Read a lot about componentWillMount function but this seems to have been deprecated. Trying to use setState to no avail.
import { useEffect, useState } from "react";
import Marquee from "react-fast-marquee";
const News = () => {
const [data, setData] = useState({});
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
const getNewsFromApi = async () => {
const response = await fetch(
"API_KEY_HERE"
);
const responseJson = await response.json();
console.log("json", responseJson);
setData(responseJson);
};
setLoading(false);
setInterval(getNewsFromApi, 1000)
}, []);
if (loading) {
return <h1> Data is loading...</h1>
}
return (
<div >
<Marquee gradientColor="" speed="120">
<h1>{data?.articles[0].title} - </h1>
<h1>{data?.articles[1].title} - </h1>
<h1>{data?.articles[2].title} - </h1>
<h1>{data?.articles[3].title} - </h1>
<h1>{data?.articles[4].title} - </h1>
<h1>{data?.articles[5].title} - </h1>
<h1>{data?.articles[6].title} - </h1>
<h1>{data?.articles[7].title} - </h1>
<h1>{data?.articles[8].title} - </h1>
</Marquee>
</div>
)
}
export default News
useEffect hook runs after the render (see docs). So what's going to happen:
You initialize loading as false (by calling useState(false)).
Component renders first time with loading=false so it skips loading placeholder and tries to immediately render articles list which is {}. It means data?.articles is undefined.
I would do the following changes:
Change your state declaration:
const [loading, setLoading] = useState(true);
This will make component to render loading placeholder first time.
2. Using state as an array of articles instead of object that is returned from your API also makes sense (as #Bru No suggested). But you'll need to make changes in your effect - something like:
setData(responseJson.articles);
you start read infos from state before the api response i suggest to use this solution
import { useEffect, useState } from "react";
import Marquee from "react-fast-marquee";
const News = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
(async function getNewsFromApi(){
const response = await fetch(
"API_KEY_HERE"
);
const responseJson = await response.json();
console.log("json", responseJson);
setData(responseJson);
})();
setLoading(false);
}, []);
if (loading) {
return <h1> Data is loading...</h1>
}
return (
<div >
<Marquee gradientColor="" speed="120">
{data.length > 0 && data.articles.map((item)=> <h1>{item.title} </h1>)}
</Marquee>
</div>
)
}
export default News
Related
I have made a custom hook that takes url and fetches the data in json format. But when I am trying to assign the data into const users using use state, I getting the error :
'Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop'
Here is the component from where I am trying to assign:
import React, { useState } from "react";
import useFetch from "./fetchData";
import Users from "./Users";
const ASSIGN5 = () => {
const [users, setUsers] = useState();
const { data, isLoading, error } = useFetch(
"https://jsonplaceholder.typicode.com/users"
);
setUsers(data);
return (
<div className="container">
<h1 className="">Users Management App</h1>
{isLoading && <p>Loading users...</p>}
{error && <p>{error}</p>}
<Search onHandleSearch={handleSearch} />
{users && <Users users={users} />}
</div>
);
};
export default ASSIGN5;
And here is the useFetch hook:
import React, { useEffect, useState } from "react";
const useFetch = (url) => {
const [data, setData] = useState([]);
const [isloading, setIsloading] = useState(true);
const [error, setError] = useState();
useEffect(() => {
fetch(url)
.then((res) => {
if (!res.ok) {
throw Error("Fetching unsucessful");
} else {
return res.json();
}
})
.then((data) => {
setData(data);
setIsloading(false);
setError(null);
})
.catch((error) => {
setError(error.message);
setIsloading(false);
});
}, [url]);
return { data, isloading, error };
};
export default useFetch;
But it runs fine when I use data directly without assigning but I need to because have to filter the data using functions
I am expecting that the data will assigned to the const users
Don't call state setters unconditionally in the component body or that'll trigger infinite renders.
It appears you don't need the users state at all because it's just an alias of the data array returned by your useFetch hook.
const ASSIGN5 = () => {
const { data, isLoading, error } = useFetch(
"https://jsonplaceholder.typicode.com/users"
);
return (
<div className="container">
<h1 className="">Users Management App</h1>
{isLoading && <p>Loading users...</p>}
{error && <p>{error}</p>}
<Search onHandleSearch={handleSearch} />
{data?.length && <Users users={data} />}
</div>
);
};
If you want to rename it you can use
const { data: users, isLoading, error } = useFetch(...);
// now you can replace `data` with `users`
Search and handleSearch weren't defined but I assume those are in your actual code somewhere.
Components are typically PascalCase, so ASSIGN5 should be Assign5.
I am using useEffect and useState hooks to fetch data and destructure it. But I'm getting this error every time.
Here is the code.
import React, { useState, useEffect } from 'react';
import { FaAngleDoubleRight } from 'react-icons/fa';
import Jobs from './Jobs';
// ATTENTION!!!!!!!!!!
// I SWITCHED TO PERMANENT DOMAIN
const url = 'https://course-api.com/react-tabs-project';
function App() {
const [loading, setLoading] = useState(true);
const [jobs, setJobs] = useState([]);
const [value, setValue] = useState(0);
const fetchJobs = async () => {
const response = await fetch(url);
const newJobs = await response.json();
setJobs(newJobs);
setLoading(false);
// console.log(newJobs);
};
useEffect(() => {
fetchJobs();
}, []);
const{company, dates, duties, title}=jobs[value];
console.log(jobs[value]);
// const { company, dates, duties, title } = jobs[value];
return (
<section className='section '>
<div className='title'>
<h2>experience</h2>
<div className='underline'></div>
</div>
{/* <Jobs jobs={jobs} /> */}
</section>
);
}
export default App;
Error image
If I comment out the destructuring, I get the value 6 times. The First 2 times it is undefined.
browser console
You are destructuring properties from the object when still the data is not fetched and the array length is 0
import React, { useState, useEffect } from "react";
import { FaAngleDoubleRight } from "react-icons/fa";
import Jobs from "./Jobs";
// ATTENTION!!!!!!!!!!
// I SWITCHED TO PERMANENT DOMAIN
const url = "https://course-api.com/react-tabs-project";
function App() {
const [loading, setLoading] = useState(true);
const [jobs, setJobs] = useState([]);
const [value, setValue] = useState(0);
const [currentJob, setCurrentJob] = useState();
const fetchJobs = async () => {
const response = await fetch(url);
const newJobs = await response.json();
setJobs(newJobs);
setLoading(false);
if (newJobs.length > 0) setCurrentJob(newJobs[value]);
// console.log(newJobs);
};
useEffect(() => {
fetchJobs();
}, []);
// const{company, dates, duties, title}=jobs[value];
// console.log(jobs[value]);
if (loading) return <h2>Loading...</h2>;
return (
<section className="section ">
<div className="title">
<h2>experience</h2>
<div className="underline"></div>
</div>
{/* <Jobs jobs={jobs} /> */}
</section>
);
}
export default App;
I have added another state variable currentJob which will assume the job item based on value variable when successfully the fetch is completed, although I would suggest to use the jobs array directly based on your component requirements.
I am not able to find where is the issue with this custom hook?
import { useState, useEffect } from "react";
const SAMPLE_DATA_URL = "../feed/sample.json";
const useFetch = () => {
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
const doFetch = async () => {
setLoading(true);
await fetch(SAMPLE_DATA_URL)
.then(res => res.json())
.then(jsonData => {
setResponse(jsonData);
})
.catch(err => setError(err))
.finally(() => setLoading(false));
};
doFetch();
},[]);
return { response, error, loading };
};
export default useFetch;
on network tab I can see 200 OK but the preview is saying "You need to enable JavaScript to run this app." and also the response is html of my index screen. I checked javascript in browser is allowed and the json file is a valid json.
on return object I am getting error: true
Where its been used
import React from "react";
import styles from "./Series.module.css";
import { TitleBar } from "../../atoms";
import {useFetch} from '../../utils';
const Series = () => {
const { response, loading, error } = useFetch();
return (
<div >
<TitleBar>Popular Series</TitleBar>
<div className={styles.content}>
{loading && <p>Loading...</p>}
{error && <p>Oops, Something went wrong...</p>}
{response && <p>response</p>}
</div>
</div>
);
};
export default Series;
If you are using CRA, you can put your sample.json inside your public folder and so you can fetch the URL directly:
fetch("sample.json")
.then(...)
.then(...)
Although, you don't need to do all that as you can just import the data like any other js modules
import data from "./sample.json"; // Path
const App = () => {
return (
<div className="App">
{data.map(item => {
// return JSX with item...
})}
</div>
);
};
codesandbox examples.
I am trying to take a tutorial for an infinite scroll using vanilla javascript and use react. To get a better understanding of how react works. I can fetch the data display the initial data. Scroll to the bottom fetch more data but the data just over riders the current data. Also I can only fetch up to page 2 I would love if someone could point me in the right direction.
import React, { useState, useEffect } from "react";
import "./App.css";
function App() {
const [posts, setPosts] = useState([{}]);
const [isFetching, setIsFetching] = useState(false);
let limit = 5;
let page = 1;
const getPosts = async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts?_limit=${limit}&_page=${page}`
);
const data = await response.json();
setPosts(data);
console.log(data);
};
function handleScroll() {
if (
window.innerHeight + document.documentElement.scrollTop !==
document.documentElement.offsetHeight
)
return;
setIsFetching(true);
}
function getMorePosts() {
setTimeout(() => {
page++;
setPosts([{ ...posts }], posts);
setIsFetching(false);
}, 2000);
}
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
useEffect(
() => {
getPosts();
}, //eslint-disable-next-line
[]
);
useEffect(() => {
if (!isFetching) return;
getMorePosts();
}, [isFetching]);
return (
<div className="App">
{posts.map((post, index) => (
<div key={index} className="post">
<div className="number">{post.id}</div>
<div className="post-info">
<h2 className="post-title">{post.title}</h2>
<p className="post-body">{post.body}</p>
</div>
</div>
))}
{isFetching && (
<div className="loader">
<div className="circle"></div>
<div className="circle"></div>
<div className="circle"></div>
</div>
)}
</div>
);
}
export default App;
One thing I noticed off the bat is that page is not in the state so it will be reset on every render. Also since limit is not changing you should use a constant.
Why are you initializing this to an array with an empty object in it? useState([{}]); just use an empty array
Also I'm not sure what you are intending to do here setPosts([{ ...posts }], posts); but if you want to append the new posts while copying the objects you should do this
const getPosts = async () => {
setIsFetching(true)
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts?_limit=${limit}&_page=${page}`
);
const data = await response.json();
setPosts([...posts, ...data]);
setIsFetching(false)
};
function getMorePosts() {
setTimeout(() => {
setPage(page++)
getPosts();
}, 2000);
}
I'm trying to query data from the Prismic headless CMS API and running into problems using React Hooks. The prismic API is returning null, though I know its being passed down correctly as I can query it successfully without using react hooks.
Heres my current compontent code. Its returning "cannot read property 'api' of null". It doesn't reach the 'data' console log.
const Footer = ({ prismicCtx }) => {
const [links, setLinks] = useState([]);
useEffect(() => {
const fetchLinks = async () => {
const data = await prismicCtx.api.query([
Prismic.Predicates.at('document.tags', [`${config.source}`]),
Prismic.Predicates.at('document.type', 'footer'),
]);
console.log('data:', data);
setLinks(data.results[0].data);
};
fetchLinks();
}, []);
return (
<div>
<h1> Footer </h1>
</div>
);
};
export default Footer;
It seems to be a case where on initial render prismicCtx is null and only on the subsequent render you receive the updated value. The solution is obviously to call the effect on change of prismicCtx, but you if you just want to call the api on initial render you would need to keep track of whether you called the api earlier or not which you can achieve by using useRef and also you don't need to set the state as empty if prismicCtx doesn't exist
const Footer = ({ prismicCtx }) => {
const [links, setLinks] = useState([]);
const isFirstCall = useRef(true);
useEffect(() => {
if(prismicCtx && isFirstCall.current) {
const fetchLinks = async () => {
const data = await prismicCtx.api.query([
Prismic.Predicates.at('document.tags', [`${config.source}`]),
Prismic.Predicates.at('document.type', 'footer'),
]);
console.log('data:', data);
setLinks(data.results[0].data);
};
fetchLinks();
isFirstCall.current = false;
}
},[prismicCtx]);
return (
<div>
<h1> Footer </h1>
</div>
);
};
export default Footer;
Figured it out, I beleive. PrismicCTX was being changed up the tree so it was switching to undefinded. A simple if/else fixed it and making it so it only updated on that prop change. Still not sure if best practice though!
const Footer = ({ prismicCtx }) => {
const [links, setLinks] = useState([]);
useEffect(
() => {
const fetchLinks = async () => {
const data = await prismicCtx.api.query([
Prismic.Predicates.at('document.tags', [`${config.source}`]),
Prismic.Predicates.at('document.type', 'footer'),
]);
console.log('data:', data);
setLinks(data.results[0].data);
};
if (prismicCtx) {
fetchLinks();
} else {
setLinks([]);
}
},
[prismicCtx]
);
return (
<div>
<h1> Footer </h1>
</div>
);
};
export default Footer;