React useEffect Infinite loop when fetching from api - reactjs

I'm trying to store the data from my fetch request into a state, but it ends up infinitely looping even when there's no dependency. When I remove the code inside the div where I filter out and map out the array it seems to not infinitely loop but I have no clue as to why filtering/mapping would cause it to loop like that
function App() {
const [searchTerm, setSearchTerm] = useState("");
const [students, setStudents] = useState([]);
const [isActive, setIsActive] = useState(false);
const average = (array) => {
return array.reduce((a, b) => a + parseFloat(b), 0) / array.length;
};
useEffect(() => {
const api = async () => {
await fetch("https://api.hatchways.io/assessment/students")
.then((res) => res.json())
.then((data) => setStudents(data.students));
};
api();
}, []);
return (
<div className='bg-gray-300 h-screen flex items-center justify-center'>
{students
.filter((student) => {
if (searchTerm === "") {
return student;
} else if (
student.firstName
.toLowerCase()
.includes(searchTerm.toLowerCase()) ||
student.lastName.toLowerCase().includes(searchTerm.toLowerCase())
) {
return student;
}
})
.map((student, i) => (
<div
key={i}
className='flex items-center space-x-8 px-8 py-3 border-b'>
<div className='flex items-start w-full space-x-7'>
<div className='border overflow-hidden rounded-full'>
<img
className='w-24 h-24 bg-contain'
src={student?.pic}
alt='student school portrait'
/>
</div>
<div className='flex flex-col justify-between space-y-4 w-full'>
<div className='flex items-center justify-between'>
<h1 className='font-bold text-5xl'>
{student?.firstName} {student?.lastName}
</h1>
<button onClick={setIsActive(!isActive)}>
{isActive ? (
<AiOutlinePlus className='h-7 w-7' />
) : (
<AiOutlineMinus className='h-7 w-7' />
)}
</button>
</div>
<div className='pl-3'>
<p>Email: {student?.email}</p>
<p>Company: {student?.company}</p>
<p>Skill: {student?.skill}</p>
<p>Average: {average(student?.grades).toFixed(2)}%</p>
</div>
</div>
</div>
</div>
))}
</div>
);
}

If you use async/await you don't need to chain .then() .
Try updating your useEffect as :
useEffect(() => {
api();
}, []);
const api = async () => {
let res = await fetch("https://api.hatchways.io/assessment/students");
let data = await res.json();
setStudents(data.students)
};
Also, Use arrow function in the button click handler as:
<button onClick={()=>setIsActive(!isActive)}>

mostly I try to call function inside useEffect while code to that fucntion outside of useEffect. it works for me try that.
useEffect(() => {
api();
}, []);
const api = async () => {
await fetch("https://api.hatchways.io/assessment/students")
.then((res) => res.json())
.then((data) => setStudents(data.students));
};

Try with async-await syntax
useEffect(() => {
const fetchData = async () => {
const response = await fetch(
"https://api.hatchways.io/assessment/students"
);
const result = await response.json();
console.log("res", result);
};
fetchData();
}, []);
In case if you want to handle errors, you need to add try catch block.

The issue was not setting the arrow function on the onclick of the button: <button onClick={() => setIsActive(!isActive)}>

<button onClick={setIsActive(!isActive)}> This may be the culprit. You're changing the state in every rander. You should instead pass () => setIsActive(!isActive) as onClick handler.

Related

Array.map not reconginizing the array and not printing data

I have a simple function component, where I am making a .get() call and then later in my jsx I am just mapping through using .map() an array to print data.
But for some reason, the array is not recognizable and it is not printing the data.
useEffect() is working fine. It is logging out data. But, the books array is just not working and I'm unable to figure out why
below is the code.
const [books, setBooks] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
useEffect(() => {
setLoading(true);
axios
.get("database.json")
.then((response) => {
const request = response.data.results.books;
console.log("request", request);
setBooks(books);
})
.catch(function (error) {
// handle error
console.log(error);
})
.finally(() => {
setLoading(false);
});
}, []);
if (loading) {
return <p>Data is loading...</p>;
}
if (error || !Array.isArray(books)) {
return <p>There was an error loading your data!</p>;
}
return (
<div className="row">
<h2>LOREN IPSUM</h2>
<div className="row__posters">
{books.map((book) => (
<img
// onClick={() => handleClick(book)}
key={book.title}
className="row__poster row__posterLarge"
src={book.book_image}
alt={book.title}
/>
))}
</div>
{/* {description && <span /> />} */}
</div>
);
}
setBooks(books);
You are updating the books with their current value.
=> Change this to setBooks(request);

Not able to access react State inside a function

I am new to javascript and react. I am trying to figure out why blockHeight state variable is not accessible inside the loadNewBlocks function which triggers when the user scrolls.
Current value of blockHeight is 0 but I am expecting this value which is set in setBlockHeight(data[data.length - 1].Height); in side useEffect. For e.g. value set inside the setBlockHeight is 14789 so I am expecting 14789 inside loadNewBlocks function.
import { useState, useEffect } from "react";
import connect from "../../Backend/database/dbtest";
export default function test({ data }) {
const [blocks, setBlocks] = useState([]);
const [blockHeight, setBlockHeight] = useState(0);
console.log("top block height: ", blockHeight);
const loadNewBlocks = async () => {
console.log(
`Value in loadNewBlocks http://localhost:3000/api/fetchBlocks?blockHeight=${blockHeight}`
);
const res = await fetch(
`http://localhost:3000/api/fetchBlocks?blockHeight=${blockHeight}`
);
if (!res.ok) {
console.log("Error in fetching blocks");
return;
}
const newBlocks = await res.json();
setBlockHeight(newBlocks[newBlocks.length - 1].Height);
setBlocks((prevBlocks) => [...prevBlocks, ...newBlocks]);
};
// Load Data on Scroll
const handleScroll = async (e) => {
if (
e.target.documentElement.scrollTop + window.innerHeight >=
e.target.documentElement.scrollHeight
) {
loadNewBlocks();
}
};
useEffect(() => {
setBlocks(data);
setBlockHeight(data[data.length - 1].Height);
console.log("useEffect blockHeight", blockHeight);
}, [data]);
useEffect(() => {
window.addEventListener("scroll", handleScroll);
}, []);
return (
<div>
<button className="border-2 bg-red-400"> Submit </button>
{blocks.map((block) => (
<div key={block.blockHeader.blockhash}>
{block.blockHeader.blockhash}
</div>
))}
</div>
);
}
export async function getServerSideProps() {
const connection = await connect();
const res = await fetch("http://localhost:3000/api/fetchBlocks");
const data = await res.json();
return {
props: { data },
};
}
Here is the updated solution. Used useRef to maintain the value.
import Link from "next/link";
import connect from "../../Backend/database/dbtest";
import { useEffect, useState, useRef, useCallback } from "react";
// import read from "../../Backend/database/read";
export default function Blocks({ data }) {
const [blocks, setBlocks] = useState([]);
const HeightRef = useRef();
const isLoading = useRef(false);
const MINUTE_MS = 500000;
const loadNewBlocks = async () => {
if (!isLoading.current) {
isLoading.current = true;
console.log(
`http://localhost:3000/api/fetchBlocks?blockHeight=${HeightRef.current}`
);
const res = await fetch(
`http://localhost:3000/api/fetchBlocks?blockHeight=${HeightRef.current}`
);
const newBlocks = await res.json();
console.log("New Blocks: ", newBlocks);
HeightRef.current = newBlocks[newBlocks.length - 1].Height;
console.log("New Height: ", HeightRef.current);
setBlocks((prevBlocks) => [...new Set([...prevBlocks, ...newBlocks])]);
isLoading.current = false;
}
};
const handleScroll = async (e) => {
if (
e.target.documentElement.scrollTop + window.innerHeight >=
e.target.documentElement.scrollHeight
) {
await loadNewBlocks();
}
};
useEffect(() => {
setBlocks(data);
HeightRef.current = data[data.length - 1].Height;
window.addEventListener("scroll", handleScroll);
}, []);
return (
<div className="bg-black flex justify-center pt-[2em]">
<div className="w-full h-full bg-gradient-to-r from-indigo-700 to-sky-600 rounded-2xl text-white grid grid-rows-[4em_1fr] mx-[6em]">
<div className=" text-4xl font-bold pl-[1em] pt-[1em]">
Latest Blocks
</div>
<div className="pt-[2em]">
<div className="grid grid-cols-[1fr_3fr_1fr_1fr] font-bold h-[3em] text-xl border-b-2">
<div className="flex justify-center"> Block Height </div>
<div className="flex justify-center">Block Header</div>
<div className="flex justify-center"> Transactions </div>
<div className="flex justify-center"> Block Size </div>
</div>
{blocks.map((block) => (
<div
key={block.blockHeader.blockhash}
className="cursor-pointer grid grid-cols-[1fr_3fr_1fr_1fr] border-b-[1px] h-[4em] pt-[1em] hover:bg-gradient-to-r from-purple-600 to-blue-400 rounded-2xl"
>
<div className="flex justify-center"> {block.Height} </div>
<div className=" ">
<Link href={`/block?blockhash=` + block.blockHeader.blockhash}>
<div className="flex justify-start px-[2em]">
{block.blockHeader.blockhash}
</div>
</Link>
</div>
<div className="flex justify-center"> {block.TxCount} </div>
<div className="flex justify-center"> {block.BlockSize} </div>
</div>
))}
</div>
</div>
</div>
);
}
export async function getServerSideProps() {
const connection = await connect();
// const blocks = JSON.parse(JSON.stringify(await read.main(false, false, 20)));
const res = await fetch("http://localhost:3000/api/fetchBlocks");
const data = await res.json();
return {
props: { data },
};
}
To try to bring
useEffect(() => {
window.addEventListener("scroll", handleScroll);
- setBlocks(data);
- setBlockHeight(data[data.length - 1].Height);
}, []);
change to
useEffect(() => {
setBlocks(data);
setBlockHeight(data[data.length - 1].Height);
}, [data]);
useEffect(() => {
window.addEventListener("scroll", handleScroll);
}, []);
Make sure that only one thing is doing
By the way, a better approach would be to throttle your scrolling function
you can use the lodash throttle method
So, I expect you to do this to maintain application optimization
const MAX_TIMES = 300
const handleScroll = () => {}
useEffect(() => {
const throttleScroll = throttle(handleScroll, MAX_TIMES);
window.addEventListener('scroll', throttleScroll);
return () => {
window.removeEventListener('scroll', throttleScroll)
}
}, [])
Good Luck :)

TypeError: Cannot destructure property 'uw' of 'uniqueWords' as it is undefined

Trying to pass down an array of uniqueWords.
On Charts initial mount uniqueWords comes in as undefineed,
I try to do a ( uniqueWords && uniqueWords) check to no success.
Although in Filter where I map through uniqueWords I use the same check and it works.
I know this may be a simple question but I am baffled.
Home
const Home = () => {
const [uniqueWords, setUniqueWords] = useState()
const [filter, setFilter] = useState(null)
const handleData = () => {
const categoryData = data.map(word => word["Parent Category"])
const uw = [...categoryData.reduce((map, obj) => map.set(obj, obj), new Map()).values()]
setUniqueWords(uw)
}
useEffect(() => {
handleData()
}, [])
return (
<div className={`w-screen h-screen bg-[#121212] text-xl text-gray-400 overflow-x-hidden`}>
<Filter
setFilter={setFilter}
uniqueWords={uniqueWords}
/>
<div className={`flex h-[70%]`}>
<Charts
uniqueWords={uniqueWords}
/>
</div>
<div className={`flex-grow bg-slate-900`}>
<DataTable filter={filter}/>
</div>
</div>
)
}
Charts - undefined error
const charts = ({uniqueWords}) => {
const [data, setData] = useState([])
const {uw} = uniqueWords && uniqueWords
const fdata = () => {
for (let i = 0; i <= uniqueWords[i].length; i++) {
setData(mdata.filter(items => items.name === uniqueWords[i]))
console.log('test')
}
}
useEffect(() => {
fdata()
}, [])
Filter - working check
const Filter = ({setFilter, uniqueWords}) => {
const handleClick = (item) => {
setFilter(item.item.toLowerCase())
}
const handleReset = () => {
setFilter(null)
}
return (<div className={`absolute top-4 left-4 flex-col shadow-xl z-20 h-min w-max`}>
<div className={`p-4 bg-slate-900`}>
{uniqueWords && uniqueWords.map(item =>
<div key={Math.random()} className={`flex items-center mb-2`}>
<input type={'checkbox'}/>
<div onClick={() => handleClick({item})} className={`ml-2 text-sm`}>{item}</div>
</div>
)}
<div className={`flex items-center w-full mt-4 rounded-md bg-slate-800`}>
<div onClick={() => handleReset()}
className={`text-md w-full text-center cursor-pointer p-2`}>Reset</div>
</div>
</div>
</div>
)
}
export default Filter
You cannot destructure array as objet.
Const {uw} = someArray
Is not valid syntax. Use [] instead of {}
This is the best option I have been able to come up with.
Although it seems really hacky.
declare const of uw using state.
only run function if uw exists.
Watch for updates on uw & uniqueWords
useEffect(() => {
if (uniqueWords) {
setUw(uniqueWords)
if (uw) {
fdata()
}
}
}, [uniqueWords, uw])

how can I handle variables reacts as variable - property?

I am trying to organize my code order to handle feed as feed.* based on my endpoint API, but however react doesn't allow me to directly send functions into component, but I want something similar to feed.results, feed. count
const [initialized, setIntialized] = useState(false);
const [feed, setFeed] = useState([]);
const browserFeed = async () => {
const response = await browse();
setFeed(response.results);
setIntialized(true);
};
useEffect(() => {
if (!initialized) {
browserFeed();
}
});
export const browse = () => {
return api.get('xxxxxxxx')
.then(function(response){
return response.data // returns .count , .next, .previous, and .results
})
.catch(function(error){
console.log(error);
});
}
<div className="searched-jobs">
<div className="searched-bar">
<div className="searched-show">Showing {feed.count}</div>
<div className="searched-sort">Sort by: <span className="post-time">Newest Post </span><span className="menu-icon">▼</span></div>
</div>
<div className="job-overview">
<div className="job-overview-cards">
<FeedsList feeds={feed} />
<div class="job-card-buttons">
<button class="search-buttons card-buttons-msg">Back</button>
<button class="search-buttons card-buttons">Next</button>
</div>
</div>
</div>
</div>
If it is pagination you are trying to handle here is one solution:
async function fetchFeed(page) {
return api.get(`https://example.com/feed?page=${page}`);
}
const MyComponent = () => {
const [currentPage, setCurrentPage] = useState(1);
const [feed, setFeed] = useState([]);
// Fetch on first render
useEffect(() => {
fetchFeed(1).then((data) => setFeed(data));
}, []);
// Update feed if the user changes the page
useEffect(() => {
fetchFeed(currentPage).then((data) => setFeed(data));
}, [currentPage]);
const isFirstPage = currentPage === 1;
return (
<>
<FeedsList feeds={feed} />
{isFirstPage && (
<button onClick={() => setCurrentPage(currentPage - 1)}>Back</button>
)}
<button Click={() => setCurrentPage(currentPage + 1)}>Next</button>
</>
);
};

filter data retrieved by redux

as you can see from my code I have some props({allRecipes}) fetched by Redux, I can display them with const mapRecipe =(), but I would like to filter them by a search bar, I think the solution would be the hook useEffect, but I can't go on,
useEffect(() =>{
const res = allRecipies.filter(el => el.name.toLowerCase().includes(searchTerm))
setSearchResults(res)},[searchTerm])
give to me error: allRecipies is null.
hope someone can point me on the right direction.
here the code:
const [searchTerm, setSearchTerm] = useState("");
const [searchResults, setSearchResults] = useState([]);
const handleChange = event => {
console.log("search bar",event.target.value)
setSearchTerm(event.target.value);
}
useEffect(() =>{
const res = allRecipies.filter(el => el.name.toLowerCase().includes(searchTerm))
setSearchResults(res)
},[searchTerm])
const mapRecipe =() =>{
if(!allRecipies){return<li>no fish</li>}
else{return allRecipies.map(el =>{
return (<div className="col s12 l4" key={el._id} >
<div className="card ">
<div style={{backgroundImage:`url(${staticImage})`,height:"200px",backgroundSize:"cover"}} className="card-image ">
<a className="btn-floating halfway-fab waves-effect waves-light btn-large lime darken-2"><i className="material-icons">clear</i></a>
</div>
<span className="card-title">{el.name}</span>
<div className="card-content">
<p>{el.listOfStages[0]}</p>
</div>
</div>
</div>)
})}
}
return (
<div>
<input type="text"
placeholder="search"
value={searchTerm}
onChange={handleChange}/>
<div className="row" >
{mapRecipe()}
</div>
</div>
)
}
function mapStateToProps(state){
console.log(state);
return state
}
export default connect(mapStateToProps)(Landing)
Use null propogation to get rid of that error:
useEffect(() =>{
const res = allRecipies?.filter(el => el.name.toLowerCase().includes(searchTerm))
setSearchResults(res)
},[searchTerm])
You can read more about it here : Null Propagation Operator in JavaScript
I would do this:-
detect incoming allRecipies with useEffect & apply default searchTerm:-
another useEffect for filtering searchTerm:-
// do search
const seacrh = (allRecipies, searchTerm) => {
return allRecipies.filter(el => el.name.toLowerCase().includes(searchTerm))
}
// run when 'allRecipies' present
useEffect(() => {
(() => {
if(allRecipes) {
setSearchResult(() => search(allRecipies, ''))
}
})()
}, [allRecipies])
// run when there's changes on 'searchTerm'
useEffect(() => {
(() => {
if(searchTerm) {
setSearchResult(() => search(allRecipies, searchTerm))
}
})()
}, [searchTerm])

Resources