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 :)
Related
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])
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.
I want to update render when a special property changes. This property income from parents. I Made a useState called loader to handle codes when I have data or not. if the loader is false, my code calls API and if it is true render data.
First of all I use useEffect this way. It didn't update render
useEffect(() => {
callApi();
}, []);
After that I used useEffect this way. props.coordinates is a property that my code should update after it changes.
useEffect(() => {
callApi();
setLoader(false);
}, [props.coordinates]);
But my codes are in loops, and my API key was blocked.
Could you let me know what my mistake is ?
This my component:
import React, { useEffect, useState } from "react";
import axios from "axios";
import ForcastHour from "./ForcastHour";
import "./WeatherHourlyForcast.css";
const WeatherHourlyForcast = (props) => {
const [loader, setLoader] = useState(false);
const [hourlyForcastData, setHourlylyForcastData] = useState(null);
useEffect(() => {
callApi();
setLoader(false);
}, [props.coordinates]);
const showHourlyForcast = (response) => {
console.log("showHourlyForcast", response.data.hourly);
setHourlylyForcastData(response.data.hourly);
setLoader(true);
};
function callApi() {
let latitude = props.coordinates.lat;
let longitude = props.coordinates.lon;
const apiKey = "23422500afd990f6bd64b60f46cf509a";
let units = "metric";
let apiUrl = `https://api.openweathermap.org/data/2.5/onecall?lat=${latitude}&lon=${longitude}&appid=${apiKey}&units=${units}`;
axios.get(apiUrl).then(showHourlyForcast);
console.log("hourly", apiUrl);
}
if (loader) {
return (
<div className="row">
<div className="col-md-6">
<div className="row">
{hourlyForcastData.map(function (hourlyforcast, index) {
if (index < 4 && index > 0) {
return (
<div
className="col-4 box-weather my-auto text-center"
key={index}
>
<ForcastHour data={hourlyforcast} />
</div>
);
}
})}
</div>
</div>
<div className="col-md-6">
<div className="row">
{hourlyForcastData.map(function (hourlyforcast, index) {
if (index < 7 && index > 3) {
return (
<div
className="col-4 box-weather my-auto text-center"
key={index}
>
<ForcastHour data={hourlyforcast} />
</div>
);
}
})}
</div>
</div>
</div>
);
} else {
callApi();
return null;
}
};
export default WeatherHourlyForcast;
While adding dependencies array to the end of useEffect (or any other hook...), each render if the value is not equal to the prev one, the hook will run again.
Because props.coordinates is an object, and in JS objA != objA == true, even if the properties didn't change, React can't know that.
My suggestion is to use the values themselves (assuming they're strings either numbers and so on)
useEffect(() => {
(async () => {
await callApi();
setLoader(false);
})()
}, [props.coordinates.lat, props.coordinates.lon]);
Another thing that you might encounter is setLoader(false) will be called before callApi will be finished, therefore added async behaviour to the hook
You can write your component likes this and call the APIs when the component mount. The API calls happens when the lat, lon values are changed.
import React, { useEffect, useState } from "react";
import axios from "axios";
import ForcastHour from "./ForcastHour";
import "./WeatherHourlyForcast.css";
const WeatherHourlyForcast = (props) => {
const { coordinates : { lat, lon } } = props;
const [loader, setLoader] = useState(false);
const [hourlyForcastData, setHourlylyForcastData] = useState(null);
useEffect(() => {
callApi();
}, [lat, lon]); //It's call the API's when the lat, lon values are changed
const callApi = () => {
setLoader(true);
const latitude = lat;
const longitude = lon;
const apiKey = "23422500afd990f6bd64b60f46cf509a";
const units = "metric";
const apiUrl = `https://api.openweathermap.org/data/2.5/onecall?lat=${latitude}&lon=${longitude}&appid=${apiKey}&units=${units}`;
axios.get(apiUrl).then((response) => {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.config);
console.log("showHourlyForcast", response.data.hourly);
setHourlylyForcastData(response.data.hourly);
setLoader(false);
});
};
if (loader) {
return (
<div>
<h1>Loading...</h1>
</div>
);
}
return (
<div className="row">
<div className="col-md-6">
<div className="row">
{hourlyForcastData.map(function (hourlyforcast, index) {
if (index < 4 && index > 0) {
return (
<div
className="col-4 box-weather my-auto text-center"
key={index}
>
<ForcastHour data={hourlyforcast} />
</div>
);
}
})}
</div>
</div>
<div className="col-md-6">
<div className="row">
{hourlyForcastData.map(function (hourlyforcast, index) {
if (index < 7 && index > 3) {
return (
<div
className="col-4 box-weather my-auto text-center"
key={index}
>
<ForcastHour data={hourlyforcast} />
</div>
);
}
})}
</div>
</div>
</div>
);
};
export default WeatherHourlyForcast;
when I added eventList into my useEffect in an array like this:
const getEvents = async () => {
const res = await axios.get(`/api/v1/events/event/${teamId}`);
setEventList(res.data.Events);
};
useEffect(() => {
getEvents();
}, [eventList]);
It just keep fetching data non stop
but if I don't put eventList in there my page just fetch it one time and when I click to another component is will gone
Added Component:
Calendar.js:
const Calendar = (props) => {
const { teamId } = props;
const events = [];
const [date, setDate] = useState(new Date());
const [eventList, setEventList] = useState([]);
const [expanded, setExpanded] = useState("");
const getEvents = async () => {
const res = await axios.get(`/api/v1/events/event/${teamId}`);
setEventList(res.data.Events);
};
useEffect(() => {
getEvents();
}, []);
const handleChangeAccordion = (panel) => (event, newExpanded) => {
setExpanded(newExpanded ? panel : false);
};
const handleChangeCalendar = (value) => {
const currentDate = moment(value).format("YYYY-MM-DD");
setDate(currentDate);
const currentEvents = events.find((event) =>
moment(currentDate).isSame(event.date, "day")
);
setEventList(currentEvents ? currentEvents.events : []);
};
const [showCalendarCard, setShowCalendarCard] = useState(false);
const addEvent = () => {
setShowCalendarCard(true);
};
return (
<div className="calendar-tab">
<div className="event-view-container">
<div className="event-date">
<p className="event-date-monthday">{moment(date).format("D")}</p>
<p className="event-date-weekday">{moment(date).format("dddd")}</p>
</div>
<div className="event-list">
{eventList.map((event, index) => (
<div>
<Accordion
key={`event-${index}`}
square
expanded={expanded === `event${index + 1}`}
onChange={handleChangeAccordion(`event${index + 1}`)}
>
<AccordionSummary>
<div className="event-list-item-header">
<span className="timestart">
{moment(event.timestart, "HH:mm:ss").format("h:mm A")}
</span>
<span className="dash">-</span>
<span className="timeend">
{moment(event.timeend, "HH:mm:ss").format("h:mm A")}
</span>
<span className="title">{event.title}</span>
</div>
</AccordionSummary>
</Accordion>
<div className="event-list-item-content">
<div className="header">
<span className="announcements">Announcements</span>
<div className="plus">
<ControlPoint />
</div>
</div>
<div className="content">{event.description}</div>
</div>
</div>
))}
</div>
</div>
<div className="calendar-view-container">
<div className="event-calendar-container">
{!showCalendarCard ? (
<div>
<EventCalendar
className="event-calendar"
formatShortWeekday={(locale, date) =>
moment(date).format("dd").charAt(0)
}
tileClassName={({ date }) => {
if (events.find((x) => moment(x.date).isSame(date, "day"))) {
return "highlight";
}
}}
onChange={(value) => handleChangeCalendar(value)}
nextLabel={<NavigateNext />}
prevLabel={<NavigateBefore />}
/>
<div className="add-event">
<ControlPoint onClick={addEvent} />
</div>
</div>
) : (
<CalendarCard
setShowCalendarCard={setShowCalendarCard}
teamId={teamId}
/>
)}
</div>
</div>
</div>
);
};
export default Calendar;
When I click to another day in calendar it will disspear my list of events.
Here is the photo of my project:
Your useEffect is depending on eventList to change and by calling that function you are changing eventList, If you want to send the request only once then this solution will do
const Calendar = (props) => {
const { teamId } = props;
// const events = [];
const [date, setDate] = useState(new Date());
const [eventList, setEventList] = useState([]);
const [expanded, setExpanded] = useState("");
const getEvents = async () => {
const res = await axios.get(`/api/v1/events/event/${teamId}`);
setEventList(res.data.Events);
};
useEffect(() => {
getEvents();
}, []);
Having an empty dependant list will only trigger the function once so change
useEffect(() => {
getEvents();
}, [eventList]);
To this
useEffect(() => {
getEvents();
}, []);
Its stuck in a loop because your useEffect callback has the side-effect changing it's own dependency (eventList).
Basically instead of resetting the event list you fetched from the API, memoize the filtered list
e.g.
const filteredEventList = useMemo(() => {
return eventList.filter((event) =>
moment(date).isSame(event.date, "day")
)
}, [date, eventList])
and then use this filtered event list in your render
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>
</>
);
};