React-select-pagination not fetching data on scroll - javascript

I am using react-select with more than 20,000 options fetched from the database via Node API.
Page was not even loading .
Now, I added "react-select-async-pagination".
But the data is fetched once only.
import React, { useRef, useState, useEffect } from "react";
import Select from "react-select";
import LoadOptions from "./LoadOptions";
import { AsyncPaginate } from "react-select-async-paginate";
const TooManySelect = () => {
const [value, onChange] = useState(null);
return (
<div className="Select-options">
<label>Pdt code</label>
<AsyncPaginate
defaultOptions
value={value}
loadOptions={LoadOptions}
onChange={onChange}
/>
</div>
);
};
export default TooManySelect;
LoadOptions : Here is the api call. I am passing the count of the last row fetched every time via "prevLast" so that I can use the OFFSET prevLast ROWS inside database query.
import { useState } from "react";
const sleep = (ms) =>
new Promise((resolve) => {
setTimeout(() => {
resolve();
}, ms);
});
const baseurl = "http://localhost:5000/api";
const LoadOptions = async (search, prevOptions) => {
const [prevLast, setPrevLast] = useState(0);
const [pdtOpt, setPdtOpt] = useState([]);
await sleep(1000);
const response = await fetch(`${baseurl}/pdt/${prevLast}`);
const pList = await response.json();
const pdtList = [];
for (let i = 0; i < pList.length; i++) {
pdtList.push({ label: pList[i].pdtno, value: pList[i].pdtno });
}
setPdtOpt(pdtList);
setPrevLast(pList.length);
return {
options: pdtList,
hasMore: true
};
};
export default LoadOptions;
Here is my codesandbox link.
https://codesandbox.io/s/react-select-paginate-test-tob90j
My question is : How can we access thousands of (select) options from DB without page freeze?

I got this link while googleing.
https://blog.saeloun.com/2022/03/03/infinite-scroll-with-pagination.html#using-lazy-loading-and-pagination
It helped me came to a perfect solution.
So my current code goes like this
(Made only a few changes from the code given in the above link).
Select component:
import React, { useState } from "react";
import SelectWrapper from "./SelectWrapper";
const baseurl = "http://localhost:5000/api";
function MainSelect() {
const [options, setOptions] = useState([]);
const [selectedOption, setSelectedOption] = useState("");
const [pageNo, setPageNo] = useState(0);
const [hasNextPage, setHasNextPage] = useState(true);
const [isNextPageLoading, setIsNextPageLoading] = useState(false);
const loadOptions = async (page) => {
try {
// console.log(`Page ${page}`);
const size = 50;
setIsNextPageLoading(true);
const data = await fetch(`${baseurl}/pdt/${page}/${size}`);
const pList = await data.json();
const pdtList = [];
for (let i = 0; i < pList.length; i++) {
pdtList.push({ label: pList[i].pdtno, value: pList[i].pdtno });
}
setOptions(pdtList);
setIsNextPageLoading(false);
setHasNextPage(pdtList.length < 500);
setPageNo(page);
} catch (err) {
console.log(err);
}
};
console.log(options);
const loadNextPage = async () => {
await loadOptions(pageNo + 1);
};
return (
<div className="dropdown">
<div className="dropdown">
<div className="label">
<label>Pdt</label>
</div>
<SelectWrapper
value={selectedOption}
placeholder="Select"
isClearable
hasNextPage={hasNextPage}
isNextPageLoading={isNextPageLoading}
options={options}
loadNextPage={loadNextPage}
onChange={(selected) => setSelectedOption(selected)}
/>
</div>
</div>
);
}
export default MainSelect;
SelectWrapper that carries out the Virtualization part:
import React, { useEffect, useState } from "react";
import { FixedSizeList as List } from "react-window";
import InfiniteLoader from "react-window-infinite-loader";
import Select from "react-select";
import AutoSizer from "react-virtualized-auto-sizer";
const SelectWrapper = (props) => {
const {
hasNextPage,
isNextPageLoading,
options,
loadNextPage,
placeholder,
onChange,
value,
} = props;
const [selectedOption, setSelectedOption] = useState(value);
useEffect(() => {
setSelectedOption(value);
}, [value]);
const itemCount = hasNextPage ? options.length + 1 : options.length;
const loadMoreItems = isNextPageLoading ? () => {} : loadNextPage;
const isItemLoaded = (index) => !hasNextPage || index < options.length;
const MenuList = ({ children }) => {
const childrenArray = React.Children.toArray(children);
const Item = ({ index, style, ...rest }) => {
const child = childrenArray[index];
return (
<div
className="drop-down-list"
style={{
borderBottom: "1px solid rgb(243 234 234 / 72%)",
display: "flex",
alignItems: "center",
...style,
}}
onClick={() => handleChange(options[index])}
{...rest}
>
{isItemLoaded(index) && child ? child : `Loading...`}
</div>
);
};
return (
<AutoSizer disableHeight>
{({ width }) => (
<InfiniteLoader
isItemLoaded={(index) => index < options.length}
itemCount={itemCount}
loadMoreItems={loadMoreItems}
>
{({ onItemsRendered, ref }) => (
<List
className="List"
height={150}
itemCount={itemCount}
itemSize={35}
onItemsRendered={onItemsRendered}
ref={ref}
width={width}
overscanCount={4}
>
{Item}
</List>
)}
</InfiniteLoader>
)}
</AutoSizer>
);
};
const handleChange = (selected) => {
console.log("test");
onChange(selected);
};
return (
<Select
placeholder={placeholder}
components={{ MenuList }}
value={selectedOption}
options={options}
{...props}
/>
);
};
export default SelectWrapper;
For anyone who needs the same, I have updated the codepen as well.
https://codesandbox.io/s/react-select-paginate-test-tob90j

Related

While rendering a component it is showing an error- "Cannot update a component (`App`) while rendering a different component (`EventList`). "

I Can't render my events. Its showing this error -
"Cannot update a component (App) while rendering a different component (EventList). To locate the bad setState() call inside EventList, follow the stack trace as described in https://reactjs.org/link/setstate-in-render"
Here is EventList Component code -
import { useEffect, useState } from "react";
import EventList from "../../event-list";
import EventForm from "../event-form";
const EventAction = ({
getEventsByClockID,
addEvent,
updateEvent,
clockID,
deleteEvent,
deleteEventsByClockID,
}) => {
const [isCreate, setIsCreate] = useState(false);
const [isToggle, setIsToggle] = useState(false);
const [eventState, setEventState] = useState(null)
const handleCreate = () => {
setIsCreate(!isCreate);
}
useEffect(() => {
setEventState(getEventsByClockID(clockID, true));
}, [isToggle])
const handleToggle = () => {
setIsToggle(!isToggle);
}
return (
<div>
<div>
<button onClick={handleCreate}>Create Event</button>
<button onClick={handleToggle}>Toggle Events</button>
</div>
{isCreate && (
<>
<h3>Create Event</h3>
<EventForm
clockID={clockID}
handleEvent={addEvent}
/>
</>
)}
{isToggle && (
<>
<h3>Events of this clock</h3>
<EventList
clockID={clockID}
eventState={eventState}
deleteEvent={deleteEvent}
updateEvent={updateEvent}
deleteEventsByClockID={deleteEventsByClockID}
/>
</>
)}
</div>
)
}
export default EventAction;
Here is my App Component Code -
import ClockList from "./components/clock-list";
import LocalClock from "./components/local-clock";
import useApp from "./hooks/useApp";
import { localClockInitState } from "./initialStates/clockInitState";
const App = () => {
const {
localClock,
clocks,
updateLocalClock,
createClock,
updateClock,
deleteClock,
getEventsByClockID,
addEvent,
deleteEvent,
updateEvent,
deleteEventsByClockID,
} = useApp(localClockInitState);
return (
<div>
<LocalClock
clock={localClock}
updateClock={updateLocalClock}
createClock={createClock}
/>
<ClockList
clocks={clocks}
localClock={localClock.date}
updateClock={updateClock}
deleteClock={deleteClock}
getEventsByClockID={getEventsByClockID}
addEvent={addEvent}
deleteEvent={deleteEvent}
updateEvent={updateEvent}
deleteEventsByClockID={deleteEventsByClockID}
/>
</div>
)
}
export default App;
and Here is my useApp hook -
import { useState } from "react";
import deepClone from "../utils/deepClone";
import generateID from "../utils/generateId";
import useEvents from "./useEvents";
const getID = generateID('clock');
const useApp = (initValue) => {
const [localClock, setLocalClock] = useState(deepClone(initValue));
const [clocks, setClocks] = useState([]);
const {
// events,
// getEvents,
getEventsByClockID,
addEvent,
deleteEvent,
deleteEventsByClockID,
updateEvent,
} = useEvents();
const updateLocalClock = (data) => {
setLocalClock({
...localClock,
...data,
})
}
const createClock = (clock) => {
clock.id = getID.next().value;
setClocks((prev) => ([
...prev, clock
]))
}
const updateClock = (updatedClock) => {
setClocks(clocks.map(clock => {
if(clock.id === updatedClock.id) return updatedClock;
return clock;
}));
}
const deleteClock = (id) => {
setClocks(clocks.filter(clock => clock.id !== id));
}
return {
localClock,
clocks,
updateLocalClock,
createClock,
updateClock,
deleteClock,
getEventsByClockID,
addEvent,
deleteEvent,
updateEvent,
deleteEventsByClockID,
}
}
export default useApp;
I want to show all events incorporated with each individual clock.

Render a component only when a state array is not empty (React Router 6)

I am dealing with search parameters and redirecting of URLs in a not so pretty way (only way I could come up with). And, because of the first useEffect right under handleSubmit(), there are way too many unnecessary renders of Search component. For instance, when the page is refreshed on the search page, the Search component gets rendered 7 times (5 renders of allImages being empty, 2 renders of allImages filled with fetched images).
So, I am thinking of adding a conditional for Search component to render Search component only when allImages is not empty (when it is filled with fetched images). Let me know if this is doable.
import React from 'react'
import Navbar from './Navbar'
import create from 'zustand'
import ErrorMsg, { useError } from './ErrorMsg'
import { useEffect } from 'react'
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
// Zustand
let store = (set) => ({
input: '',
setInput: (value) => set({ input: value }),
allImages: [],
setAllImages: (images) => set({ allImages: images}),
totalResults: null,
setTotalResults: (num) => set({ totalResults: num}),
})
export const useHeader = create(store)
function Header() {
// global state and search params
let navigate = useNavigate()
const location = useLocation()
const [searchParams] = useSearchParams()
const query = searchParams.get('query')
const page = Number(searchParams.get('page') || 1)
const input = useHeader(state => state.input)
const setInput = useHeader(state => state.setInput)
const allImages = useHeader(state => state.allImages)
const setAllImages = useHeader(state => state.setAllImages)
const setTotalResults = useHeader(state => state.setTotalResults)
const error = useError(state => state.error)
const setError = useError(state => state.setError)
const showError = useError(state => state.showError)
const setShowError = useError(state => state.setShowError)
const setFadeOut = useError(state => state.setFadeOut)
function handleChange(event) {
setInput(event.target.value)
}
async function fetchImages() {
try {
const res = await fetch(`https://api.unsplash.com/search/photos?&page=${page}&per_page=30&query=${input}&client_id=${process.env.REACT_APP_UNSPLASH_API_KEY}`)
const data = await res.json()
if (data.total !== 0) {
setAllImages(data.results)
setTotalResults(data.total)
} else {
setAllImages([])
setTotalResults(0)
}
} catch(error) {
setError(error)
}
}
const handleSubmit = async (event) => {
event.preventDefault()
navigate(`/search?query=${input}&page=1`)
}
// this useEffect causes Search.js to render too many times
// especially the second conditional need improvement
useEffect(() => {
if (location.pathname === '/search' && allImages.length === 0) {
if (query) {
setInput(query)
}
navigate(`/search?query=${input}&page=${page}`)
fetchImages()
}
// need this to deal with page not refreshing when submitting or changing pages
if (location.pathname === '/search' && allImages.length !== 0) {
fetchImages()
}
// eslint-disable-next-line
}, [searchParams])
// error
useEffect(() => {
if (error) {
setShowError(true)
setTimeout(() => {
setFadeOut(true)
setTimeout(() => {
setShowError(false)
}, 1000)
}, 5000)
}
}, [error, setFadeOut, setShowError])
return (
<div className='header'>
<Navbar />
<h2 className='header--heading text-center text-light'>Find Images</h2>
<div className='header--form'>
<form onSubmit={handleSubmit}>
<input
className='header--form--input'
autoComplete='off'
type='text'
placeholder='Search'
onChange={handleChange}
name='input'
value={input}
/>
</form>
</div>
{showError && <ErrorMsg />}
</div>
)
}
export default Header
import React from 'react'
import Header from '../Header'
import Image from '../Image'
import { useHeader } from '../Header';
import { useSearchParams } from 'react-router-dom';
function Search() {
const [searchParams, setSearchParams] = useSearchParams()
const page = Number(searchParams.get('page') || 1)
const allImages = useHeader(state => state.allImages)
const totalResults = useHeader(state => state.totalResults)
console.log(allImages)
console.log('Search.js rendered')
// pages
function handlePrev() {
setSearchParams(params => {
params.set("page", Math.max(1, page - 1))
return params
})
}
function handleNext() {
setSearchParams(params => {
params.set("page", page + 1)
return params
})
}
return (
<div>
<Header />
{/* {totalResults === 0 && <p>Nothing Found</p>} */}
<div className='image-list mt-5 pb-5'>
{allImages.map(el => (
<Image
key={el.id}
// do need spread operator below for img's src to work in Image.js
{...el}
el={el}
/>
))}
</div>
{allImages.length !== 0 && <div className='pagination'>
<button disabled={page === 1} onClick={handlePrev}>
Prev
</button>
<h5 className='pagination--h5'>{page}</h5>
<button disabled={totalResults < 31} onClick={handleNext}>
Next
</button>
</div>}
</div>
)
}
export default Search
Finally figured it out and all I had to do was to improve the fetchImages function and simplify the useEffect.
import React from 'react'
import Navbar from './Navbar'
import create from 'zustand'
import ErrorMsg, { useError } from './ErrorMsg'
import { useEffect, useRef } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
// Zustand
let store = (set) => ({
input: '',
setInput: (value) => set({ input: value }),
allImages: [],
setAllImages: (images) => set({ allImages: images}),
totalResults: null,
setTotalResults: (num) => set({ totalResults: num}),
})
export const useHeader = create(store)
function Header() {
// global state and search params, and some others
let navigate = useNavigate()
const inputRef = useRef(null)
const [searchParams] = useSearchParams()
const query = searchParams.get('query')
const page = Number(searchParams.get('page') || 1)
const input = useHeader(state => state.input)
const setInput = useHeader(state => state.setInput)
const setAllImages = useHeader(state => state.setAllImages)
const setTotalResults = useHeader(state => state.setTotalResults)
const error = useError(state => state.error)
const setError = useError(state => state.setError)
const showError = useError(state => state.showError)
const setShowError = useError(state => state.setShowError)
const setFadeOut = useError(state => state.setFadeOut)
function handleChange(event) {
setInput(event.target.value)
}
const handleSubmit = async (event) => {
event.preventDefault()
navigate(`/search?query=${input}&page=1`)
}
let realShit
if (input === '') {
realShit = query
} else {
realShit = input
}
useEffect(() => {
async function fetchImages() {
try {
const res = await fetch(`https://api.unsplash.com/search/photos?&page=${page}&per_page=30&query=${realShit}&client_id=${process.env.REACT_APP_UNSPLASH_API_KEY}`)
const data = await res.json()
if (data.total === 0) {
setTotalResults(0)
} else {
setAllImages(data.results)
setTotalResults(data.total)
}
} catch(error) {
setError(error)
}
}
fetchImages()
// eslint-disable-next-line
}, [searchParams])
// input
useEffect(() => {
inputRef.current.focus()
}, [])
// error
useEffect(() => {
if (error) {
setShowError(true)
setTimeout(() => {
setFadeOut(true)
setTimeout(() => {
setShowError(false)
}, 1000)
}, 5000)
}
}, [error, setFadeOut, setShowError])
return (
<div className='header'>
<Navbar />
<h2 className='header--heading text-center text-light'>Find Images</h2>
<div className='header--form'>
<form onSubmit={handleSubmit}>
<input
className='header--form--input'
autoComplete='off'
type='text'
placeholder='Search'
onChange={handleChange}
name='input'
value={input}
ref={inputRef}
/>
</form>
</div>
{showError && <ErrorMsg />}
</div>
)
}
export default Header

Why doesn't useSelector doesn't re-execute JSX

I am making an app which should load content from api and immediately display components based on that data. Firstly, when it fetches the data, it should dispatch it to redux store and then with useSelector should trigger the change and pass the value as a prop to children component. Problem is that it does dispatch and select it with selector (there are console logs in code to confirm) but in doesn't pass it to child via props. I know that I can use useSelector in child but I am now intrested why doesn't it work this way.
Component 1
const Menu = () => {
console.log("reload");
const food = useSelector((state) => state.foodList.food);
console.log(food);
const [foodList, setFoodList] = useState(food);
// console.log(foodList);
const dispatch = useDispatch();
const page = useSelector((state) => state.ui.page);
useEffect(() => {
const getFoodList = async () => {
const response = await fetch(
"https://senbonzakura-food-default-rtdb.firebaseio.com/food.json"
);
if (!response.ok) throw new Error("Something went wrong!");
const data = await response.json();
const foodList = data[Object.keys(data)[0]];
console.log("---");
console.log(foodList);
console.log("---");
dispatch(foodSliceActions.updateFoodList(foodList));
};
try {
getFoodList();
// dispatch(foodSliceActions.updateFoodList(foodList));
} catch (err) {
console.log(err);
}
}, []);
// const navigate = useNavigate();
// const queryPrams = new URLSearchParams(location.search);
// const sort = queryPrams.get("sort");
return (
<main className={classes["menu-main"]}>
<section
className={`${classes["menu__section"]} ${classes["menu__left-side"]}`}
>
<div className={classes["menu__label"]}>
<h1>MENU</h1>
<h1>{page + 1}</h1>
</div>
<MenuList foodList={foodList ? foodList : []} page={page} />
</section>
<section
className={`${classes["menu__section"]} ${classes["menu__right-side"]}`}
>
<Outlet />
</section>
</main>
);
};
export default Menu;
Child component
const formatArray = (array) => {
const pages = Math.ceil(array.length / 5);
const arr = [];
let helpArr = [];
let c = 0;
for (let i = 0; i < pages; i++) {
for (let j = c; j < c + 5; j++) {
helpArr.push(array[j]);
}
c += 5;
arr.push(helpArr);
helpArr = [];
}
return arr;
};
const MenuList = (props) => {
const page = props.page;
const dispatch = useDispatch();
const DUMMY_FOOD = props.foodList;
console.log(DUMMY_FOOD);
const navigate = useNavigate();
const location = useLocation();
const params = useParams();
const [foodList, setFoodList] = useState([]);
useEffect(() => {
setFoodList(formatArray(DUMMY_FOOD));
}, []);
const queryPrams = new URLSearchParams(location.search);
const sort = queryPrams.get("sort");
const onNextPageHandler = () => {
dispatch(uiSliceActions.updatePage("forward"));
};
const onPreviousPageHandler = () => {
dispatch(uiSliceActions.updatePage("backward"));
};
const onSortPageHandler = () => {
navigate(`/menu/${params.foodId}/?sort=${sort === "asc" ? "desc" : "asc"}`);
sort === "asc"
? (DUMMY_FOOD = DUMMY_FOOD.sort((a, b) => a.foodPrice - b.foodPrice))
: (DUMMY_FOOD = DUMMY_FOOD.sort((a, b) => b.foodPrice - a.foodPrice));
};
return (
<Fragment>
<div className={classes["menu-list"]}>
{foodList[page]
? foodList[page].map((foodObj) => (
<MenuItem key={foodObj.id} foodObj={foodObj} />
))
: ""}
</div>
<div className={classes["menu-list__buttons"]}>
{page >= 1 && (
<Button type="button" onClick={onPreviousPageHandler}>
Page {page}
</Button>
)}
<Button type="button" onClick={onSortPageHandler}>
{sort === "asc" ? `Descending` : `Ascending`}
</Button>
<Button type="button" onClick={onNextPageHandler}>
Page {page + 2}
</Button>
</div>
</Fragment>
);
};
export default MenuList;
food-slice
import { createSlice } from "#reduxjs/toolkit";
const initialState = { food: [] };
const foodSlice = createSlice({
name: "foodList",
initialState,
reducers: {
updateFoodList(state, action) {
// console.log(action.payload);
state.food = action.payload;
},
},
});
const foodSliceActions = foodSlice.actions;
export const foodSliceReducer = foodSlice.reducer;
export default foodSliceActions;
index (store)
import { configureStore } from "#reduxjs/toolkit";
import { uiSliceReducer } from "./ui-slice";
import { cartSliceReducers } from "./cart-slice";
import { foodSliceReducer } from "./food-slice";
const store = configureStore({
reducer: {
ui: uiSliceReducer,
cart: cartSliceReducers,
foodList: foodSliceReducer,
},
});
export default store;
Thank you.
You wrote the following code in component1
const food = useSelector((state) => state.foodList.food);
const [foodList, setFoodList] = useState(food);
Here, you set food as initial state value of foodList state, so foodList is updated as food only when the component1 loads initially (componentDidMount lifecycle), instead of updating whenever food is updated.
If you wants to update foodList whenever food is updated, you need to set it in useEffect hook.
useEffect(() => { setFoodList(food) }, [food]);
Btw, you don't need to pass food as props to the child component.
You can use food directly in child component.
I updated your code.
component1
const Menu = () => {
const dispatch = useDispatch();
const page = useSelector((state) => state.ui.page);
useEffect(() => {
const getFoodList = async () => {
const response = await fetch(
"https://senbonzakura-food-default-rtdb.firebaseio.com/food.json"
);
if (!response.ok) throw new Error("Something went wrong!");
const data = await response.json();
const foodList = data[Object.keys(data)[0]];
dispatch(foodSliceActions.updateFoodList(foodList));
};
try {
getFoodList();
} catch (err) {
console.log(err);
}
}, []);
return (
<main className={classes["menu-main"]}>
<section
className={`${classes["menu__section"]} ${classes["menu__left-side"]}`}
>
<div className={classes["menu__label"]}>
<h1>MENU</h1>
<h1>{page + 1}</h1>
</div>
<MenuList page={page} />
</section>
<section
className={`${classes["menu__section"]} ${classes["menu__right-side"]}`}
>
<Outlet />
</section>
</main>
);
};
export default Menu;
child component
const formatArray = (array) => {
const pages = Math.ceil(array.length / 5);
const arr = [];
let helpArr = [];
let c = 0;
for (let i = 0; i < pages; i++) {
for (let j = c; j < c + 5; j++) {
helpArr.push(array[j]);
}
c += 5;
arr.push(helpArr);
helpArr = [];
}
return arr;
};
const MenuList = (props) => {
const page = props.page;
const dispatch = useDispatch();
const DUMMY_FOOD = useSelector((state) => state.foodList.food);
const [foodList, setFoodList] = useState([]);
const navigate = useNavigate();
const location = useLocation();
const params = useParams();
const queryPrams = new URLSearchParams(location.search);
const sort = queryPrams.get("sort");
useEffect(() => {
setFoodList(DUMMY_FOOD);
}, [DUMMY_FOOD]);
const onNextPageHandler = () => {
dispatch(uiSliceActions.updatePage("forward"));
};
const onPreviousPageHandler = () => {
dispatch(uiSliceActions.updatePage("backward"));
};
const onSortPageHandler = () => {
navigate(`/menu/${params.foodId}/?sort=${sort === "asc" ? "desc" : "asc"}`);
sort === "asc"
? (setFoodList(foodList.sort((a, b) => a.foodPrice - b.foodPrice)))
: (setFoodList(foodList.sort((a, b) => b.foodPrice - a.foodPrice)));
};
return (
<Fragment>
<div className={classes["menu-list"]}>
{foodList[page]
? foodList[page].map((foodObj) => (
<MenuItem key={foodObj.id} foodObj={foodObj} />
))
: ""}
</div>
<div className={classes["menu-list__buttons"]}>
{page >= 1 && (
<Button type="button" onClick={onPreviousPageHandler}>
Page {page}
</Button>
)}
<Button type="button" onClick={onSortPageHandler}>
{sort === "asc" ? `Descending` : `Ascending`}
</Button>
<Button type="button" onClick={onNextPageHandler}>
Page {page + 2}
</Button>
</div>
</Fragment>
);
};
export default MenuList;

Waiting for useState hook increment counter using Promises

I have a counter which needs to be updated before my DOM is rendered.
const [count, setCount] = useState(1);
I need this to be updated whenever user scrolls, and the function fetchMoreData is called.
So I added it inside Promise and waited until the same has been updated
const fetchMoreData = async () => {
await new Promise((resolve, reject) => {
setCount(count+1);
resolve();
});
updateNews();
};
But this does not seem to be working and count isn't incremented immediately.
How can I resolve the same?
EDIT : In the chat, A.mola helped me in solving the issue, so the answer he gave me was according to my code, I am posting the same here.
import React, { useEffect, useState } from "react";
import NewsItem from "./NewsItem";
import Spinner from "./Spinner";
import PropTypes from "prop-types";
import InfiniteScroll from "react-infinite-scroll-component";
const News = (props) => {
const [articles, setArticles] = useState([]);
const [loading, setLoading] = useState(true);
const [page, setPage] = useState(1);
const [totalResults, setTotalResults] = useState(0);
const capitalizeFirstLetter = (string) => {
return string.charAt(0).toUpperCase() + string.slice(1);
};
const updateNews = async () => {
props.setProgress(10);
let goToPage = page;
const url = `newsapi.org/v2/…${props.country}&category=${props.category}&apiKey=${props.apiKey}&page=${goToPage}&pageSize=${props.pageSize}`;
props.setProgress(30);
let data = await fetch(url);
props.setProgress(50);
let parsedData = await data.json();
props.setProgress(70);
if (parsedData) {
setArticles(articles.concat(parsedData.articles));
setLoading(false);
setPage(page);
setTotalResults(parsedData.totalResults);
}
props.setProgress(100);
};
useEffect(() => {
updateNews();
// eslint-disable-next-line
}, []);
const fetchMoreData = async () => {
setPage((page) => {
console.log(page);
return page + 1;
});
console.log(page);
updateNews();
};
return (
<>
<h3 className="text-center" style={{ marginTop: "4%" }}>
NewsMonkey - Top {`${capitalizeFirstLetter(props.category)}`} Headlines
</h3>
{loading && <Spinner />}
<InfiniteScroll
dataLength={articles.length}
next={fetchMoreData}
hasMore={articles.length < totalResults}
loader={<Spinner />}
>
<div className="container">
<div className="row">
{articles.map((element) => {
return (
<div className="col-md-4" key={element.url}>
<NewsItem
title={
element && element.title ? element.title.slice(0, 45) : ""
}
description={
element && element.description
? element.description.slice(0, 50)
: ""
}
imageUrl={element.urlToImage}
newsUrl={element.url}
author={element.author}
date={element.publishedAt}
source={element.source.name}
/>
</div>
);
})}
</div>
</div>
</InfiniteScroll>
</>
);
};
Thanks a.mola for the comprehensive answer, I am updating the answer that you gave me in the chat here.
We are using useEffect hook to render our component using the array dependency.
import React, { useEffect, useState } from "react";
import NewsItem from "./NewsItem";
import Spinner from "./Spinner";
import PropTypes from "prop-types";
import InfiniteScroll from "react-infinite-scroll-component";
const News = ({ country, category, apiKey, pageSize, setProgress }) => {
const [articles, setArticles] = useState([]);
const [loading, setLoading] = useState(true);
const [page, setPage] = useState(1);
const [totalResults, setTotalResults] = useState(0);
const capitalizeFirstLetter = (string) => {
return string.charAt(0).toUpperCase() + string.slice(1);
};
useEffect(() => {
const updateNews = async () => {
setProgress(10);
let goToPage = page;
const url = `newsapi.org/v2/…${country}&category=${category}&apiKey=${apiKey}&page=${goToPage}&pageSize=${pageSize}`;
setProgress(30);
let data = await fetch(url);
setProgress(50);
let parsedData = await data.json();
setProgress(70);
if (parsedData) {
setArticles(articles.concat(parsedData.articles));
setLoading(false);
setTotalResults(parsedData.totalResults);
}
setProgress(100);
};
updateNews();
}, [page, articles, pageSize, setProgress, apiKey, category, country]);
const fetchMoreData = () => setPage(page + 1);
return (
<>
<h3 className="text-center" style={{ marginTop: "4%" }}>
NewsMonkey - Top {`${capitalizeFirstLetter(category)}`} Headlines
</h3>
{loading && <Spinner />}
<InfiniteScroll dataLength={articles.length} next={fetchMoreData} hasMore={articles.length < totalResults} loader={<Spinner />}>
<div className="container">
<div className="row">
{articles.map((element) => {
return (
<div className="col-md-4" key={element.url}>
<NewsItem title={element && element.title ? element.title.slice(0, 45) : ""} description={element && element.description ? element.description.slice(0, 50) : ""} imageUrl={element.urlToImage} newsUrl={element.url} author={element.author} date={element.publishedAt} source={element.source.name} />
</div>
);
})}
</div>
</div>
</InfiniteScroll>
</>
);
};
Here is the codesandbox link : https://codesandbox.io/s/proud-shape-58yig?file=/Infinite.tsx:0-2597

Passing value up in react

I have a component with a lot of buttons, which increment the counter. I need the counter all the way up in the main component, so I want to pass the Counter value;
import { useState } from "react";
import Card from "./Card";
const ProductList = (props) => {
const products = props.products;
const [ Counter, setCounter ] = useState(0);
const Count = n => { setCounter(v => v + n) }
return (
<div className="ProductList" >
{products.map((product) => (
<Card product={product} key={product.id} C={Count} />))}
</div>
);
}
export default ProductList;
up to the parent component:
import { useEffect, useState } from "react";
import ProductList from "./ProductList";
const Products = () => {
const [products, setProducts] = useState (null);
useEffect (() => {
fetch('http://localhost:8000/products')
.then(res => {
return res.json();
})
.then(data => {
setProducts(data);
})
}, []);
return (
<div className="ProductList">
{products && <ProductList products={products}/>}
</div>
);
}
export default Products;
It's a simple question, but I'm not sure how it works. Can anyone help? Thanks!
If you need the Counter reference in the Products component you should define it in the component itself and pass the function reference down to the child component.
Try to change your code like this:
Products component:
const Products = () => {
const [products, setProducts] = useState(null);
const [Counter, setCounter] = useState(0);
const count = (n) => {
setCounter((v) => v + n);
};
useEffect(() => {
fetch('http://localhost:8000/products')
.then((res) => {
return res.json();
})
.then((data) => {
setProducts(data);
});
}, []);
return (
<div className='ProductList'>
{products && <ProductList products={products} count={count}/>}
</div>
);
};
export default Products;
ProductList component
import Card from "./Card";
const ProductList = (props) => {
const products = props.products;
const count = props.count
return (
<div className="ProductList" >
{products.map((product) => (
<Card product={product} key={product.id} C={count} />))}
</div>
);
}
export default ProductList;
However, if you need to pass the function down multiple levels, you should probably use the Context API, but from the question, I presume you just need to go down one level.
Define the counter state in the parent component then pass both the counter and setCounter to the child as props. Update counter with setCounter and it will be update in the parent component also.
Child:
import Card from "./Card";
const ProductList = ({counter, setCounter}) => {
const products = props.products;
const Count = n => { setCounter(v => v + n) }
return (
<div className="ProductList" >
{products.map((product) => (
<Card product={product} key={product.id} C={Count} />))}
</div>
);
}
export default ProductList;
Parent:
import ProductList from "./ProductList";
const Products = () => {
const [products, setProducts] = useState (null);
const [counter, setCounter] = useState(0);
useEffect (() => {
fetch('http://localhost:8000/products')
.then(res => {
return res.json();
})
.then(data => {
setProducts(data);
})
}, []);
return (
<div className="ProductList">
{products &&
<ProductList
counter={counter}
setCounter={setCounter}
products={products}
/>}
</div>
);
}
export default Products;

Categories

Resources