Pagination in NextJs - pagination

I am trying to paginate one of my pages in the application which is built with React / NextJs - getServerSideProps.
Step 1: Creates a pagination component
Step 2: Redirect to a URL with Page numbers (based on user clicks)
Step 3: It should re-render getServerSideProps with the newer page value, which is not happening right now.
My current code block (Server Side Props - API call):
export const getServerSideProps = async (ctx) => {
try {
const APIKey = await getCookieAPIKey(ctx);
const user = await getCookieUser(ctx);
const dataSSR = await getDataSSR(
APIKey,
'/xyz/xyz/read/',
user.user_id,
'user_id',
ctx.query.page,
ctx.query.limit
);
// console.log(d, "data")
return {
props: {
dataSSR
}
};
} catch (err) {
...
return { props: { fetchError: err.toString() } };
}
};
export const getDataSSR = async (APIKey, path, id, idString, page, limit) => {
//generate URL path for fetch
const base_url = `${ENDPOINT}/services`;
let url;
if (id && !idString && !page) {
url = base_url + path + '?key=' + APIKey + '&id=' + id;
} else if (id && idString && page) {
url = base_url + path + '?key=' + APIKey + `&${idString}=` + id + '&page=' + page + `&limit=${!limit ? '24' : limit}`;
} else if (id && idString && !page) {
url = base_url + path + '?key=' + APIKey + `&${idString}=` + id + '&page=0' + `&limit=${!limit ? '24' : limit}`;
}
else {
url = base_url + path + '?key=' + APIKey + '&page=' + page + `&limit=${!limit ? '10' : limit}`;
}
I followed this tutorial for pagination.
With a modification of the click method statement:
<ReactNextPaging
itemsperpage={itemsperpage}
nocolumns={nocolumns}
items={items}
pagesspan={pagesspan}
>
{({
getBackButtonProps,
getFwdButtonProps,
getFastFwdButtonProps,
getSelPageButtonProps,
nopages,
inipagearray,
pagesforarray,
currentpage,
noitems,
initialitem,
lastitem,
goBackBdisabled,
goFastBackBdisabled,
goFwdBdisabled,
goFastFwdBdisabled
}) => (
<tbody style={{ alignItems: "center", margin: "auto auto" }}>
{/* {items.slice(initialitem, lastitem).map((item, index) => {
return item;
})} */}
{noitems > 0
? [
<tr key={"pagingrow" + 100} >
<td colSpan={nocolumns} style={{ textAlign: "center" }}>
<button
style={buttonStyles(goBackBdisabled)}
{...getBackButtonProps()}
disabled={goBackBdisabled}
>
{"<"}
</button>
{Array.from(
{ length: pagesforarray },
(v, i) => i + inipagearray
).map(page => {
return (
<button
key={page}
{...getSelPageButtonProps({ page: page })}
disabled={currentpage == page}
style={{ margin: "0.5em", backgroundColor: "transparent", border: "none" }}
onClick={e => page != currentpage ? pageNumClick(page, e, currentpage) : {}}
>
{page}
</button>
);
})}
<button
style={buttonStyles(goFwdBdisabled)}
{...getFwdButtonProps()}
disabled={goFwdBdisabled}
>
{">"}
</button>
</td>
</tr>
]
: null}
</tbody>
)}
</ReactNextPaging>
Page redirection handle code :
const pageNumClick = (page, e, currentpage) => {
let el = document.getElementsByClassName(`.clickable-page-${page}`)
console.log(el)
e.target.style.backgroundColor = "#353E5A";
currentpage = page;
console.log(page, "clicked page number", e.target, currentpage)
//Redirects to the URL with clicked page number
router.push({
pathname: router.pathname,
query: { show: showname, page: page }
})
refreshData(); // Try to refresh props once the URL is changed
}
const refreshData = () => {
router.replace(router.asPath);
console.log('refreshed')
}
Attempts to resolve:
Added refreshData method to invoke ServerSideProps upon URL change based on this.
Tried changing getServerSideProps to getInitialProps - with no luck
Any help or links would be appreciated, been stuck with the task since 3 days

Issue is caused by the refreshdata function, router.asPath will have your current url.
Below code is working fine for me.
function ProductDetail({ products, page,limit }) {
const router = useRouter();
const pageNumClick = (page, limit) => {
router.push({
pathname: router.pathname,
query: { limit: limit, page: page },
});
};
return (
<div>
<div onClick={() => pageNumClick(parseInt(page) + 1, limit)}>Next page</div>
<div onClick={() => pageNumClick(parseInt(page) - 1, limit)}>
Previous page
</div>
{products ? JSON.stringify(products) : <></>}
</div>
);
}
export async function getServerSideProps({ params, query, ...props }) {
const products = await getProducts(query.limit, query.page);
return {
props: {
products: products ? products : {},
page: query.page,
limit: query.limit,
},
};
}

Related

Data not reloading until refresh reactJS

I am currently building a website with two views in which a user can toggle between both views (images seen below) On both views I am allowing a user to update a database and both views display data regarding the state of that database. However, when I update the data on one toggle view of the data and switch to the other view, I can't see the updated data until I refresh.
I am wrapping my react application around this user context provider shown below
const [isLoading, setIsLoading] = useState(true);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [user, setUser] = useState();
const [courseList, setCourseList] = useState();
const [semesterList, setSemesterList] = useState();
const [userMajor, setMajor] = useState();
const checkContext = useCallback(() => {
axios
.get("/auth/check", { withCredentials: true })
.then(({ data }) => {
if (data.auth) {
setIsAuthenticated(true);
setUser(data.user);
setCourseList(data.courseList);
setSemesterList(data.semesterList);
setMajor(data.major);
setIsLoading(false);
} else {
setIsAuthenticated(false);
setMajor(undefined);
setCourseList(undefined);
setSemesterList(undefined);
setUser(undefined);
setIsLoading(false);
}
})
.catch(() =>
console.log(
"Something went wrong while trying to fetch your auth status."
)
);
}, []);
useEffect(() => {
checkContext();
}, [checkContext]);
return (
<UserContext.Provider
value={{
isLoading,
isAuthenticated,
user,
courseList,
semesterList,
userMajor,
checkContext,
}}
>
{children}
</UserContext.Provider>
);
};
export default UserContextProvider;
This is where I toggle between views
const Home = () => {
const [selected, setSelected] = useState(false);
const handleChange = () => {
setSelected(!selected);
setSelected(!selected);
};
let view;
if (!selected) {
view = <CardView />;
} else {
view = <SemesterView />;
}
return (
<>
<ToggleButton
className="toggler"
value="toggle"
selected={selected}
onChange={handleChange}
>
<ViewModuleIcon />
</ToggleButton>
{view}
</>
);
};
export default Home;
For keeping this shorter, here is one of views in which I update the database using a checkbox form.
const Home = () => {
const { courseList } = useContext(UserContext);
const initialMajorState = {
majorName: "",
majorCode: "",
majorComponentFamilies: [],
};
const [majorData, setMajorData] = useState(initialMajorState);
const [checkedCourses, setCheckedCourses] = useState(courseList);
const [componentLimit, setComponentLimit] = useState([]);
const [componentFamilyLimit, setComponentFamilyLimit] = useState([]);
const [isLoadingData, setIsLoadingData] = useState(true);
/**
* updates the checked courses, the database, as well as the limits for both components and componentfamilys
* #param checkedCourse is the course that is being checkedd
* #param checkedComponentName is the course family name that is being updated
* #param componentIndex is the course component to which the course belongs to
* #param componentFamilyIndex is which componentFamily the course belongs to
*/
const handleOnChange = (
checkedCourse,
checkedComponentName,
componentIndex,
componentFamilyIndex
) => {
// copy the componentLimit array
const updatedComponentLimit = [...componentLimit];
// copy the componentFamilyLimit array
const updatedComponentFamilyLimit = [...componentFamilyLimit];
const updatedCheckedCourses = checkedCourses?.includes(checkedCourse)
? checkedCourses?.filter((name) => name !== checkedCourse)
: [...(checkedCourses ?? []), checkedCourse];
let difference = updatedCheckedCourses.filter(
(x) => !checkedCourses.includes(x)
);
// This means that we added the course
if (difference.length > 0) {
// Add the course to the databas
UserCourseService.add(checkedCourse);
// Add the course to the checked list for that component
updatedComponentLimit[componentIndex].checked.push(checkedCourse);
// if that component is now satisfied then we want to add that to the component family's satisfied components
if (
updatedComponentLimit[componentIndex].checked.length >=
updatedComponentLimit[componentIndex].limit
) {
updatedComponentFamilyLimit[
componentFamilyIndex
].checkedComponentFamily.push(checkedComponentName);
}
}
// if the difference length is none then that means we unchecked a box
else {
// Delete the course from our database
UserCourseService.delete(checkedCourse);
// Add the course to the updatedComponentLimit array at the component index where course lies
updatedComponentLimit[componentIndex].checked.pop(checkedCourse);
// Pop that component family (pop will return null if not found anyways)
updatedComponentFamilyLimit[
componentFamilyIndex
].checkedComponentFamily.pop(checkedComponentName);
}
// set the componentFamilyLimit array and componentLimit array and checked courses
setComponentLimit(updatedComponentLimit);
setComponentFamilyLimit(updatedComponentFamilyLimit);
setCheckedCourses(updatedCheckedCourses);
};
/**
* returns a boolean determining if a checkbox is disabled or not
* #param course deals with which course we are checking should be disabled or not
* #param componentIndex deals with which component we are looking at
* #param componentFamilyIndex deals with which compoentFamily we are looking at
*/
const isDisabled = (course, componentIndex, componentFamilyIndex) => {
// if the component family limit has been reached then just return if the checkbox has already been checked
if (
componentFamilyLimit[componentFamilyIndex].checkedComponentFamily
.length >= componentFamilyLimit[componentFamilyIndex].limit
)
return componentLimit[componentIndex].checked.indexOf(course) === -1;
// if the component family limit has not been reached then check if the component itself has been meet and
// if the element has been checked already
else {
return (
componentLimit[componentIndex].checked.length >=
componentLimit[componentIndex].limit &&
// If not already checked
componentLimit[componentIndex].checked.indexOf(course) === -1
);
}
};
/**
* gets the data after searching for a course in the searchbar from the backend. Initializes the limit arrays and checked courses
* #param query is the name of the major we are getting data from in the backend
*/
const getData = async (query) => {
// Initializing the state for our global hooks in this function
var newCheckedComponentList = [];
var newCheckedComponentFamilyList = [];
// gets the response data in regards to the major we are interested in from the backend
MajorDataService.find(query)
.then((response) => {
// sets the major data to the response data
setMajorData(response.data);
// if the data exists then we want to initialize the other stuff
if (response.data.majorComponentFamilies) {
// Looping through the component families
response.data.majorComponentFamilies.forEach((componentFamily) => {
// gets the limit from the component family
var componentFamilyLimit = parseInt(
componentFamily.required_num_components
);
var newComponentFamilyList = [];
// Loops through each of the components in each component family
componentFamily.component_list.forEach((component) => {
// courselimit on each component
var courseLimit = parseInt(component.required_num_courses);
// the course list for a component
var newUserCourseList = [];
// Loops through all of the courses in each component
component.course_list.forEach((course) => {
// if the courselist includes the course then add that to the course list
if (courseList.includes(course)) {
newUserCourseList.push(course);
}
});
// Add to the checked array for components
newCheckedComponentList.push({
checked: newUserCourseList,
limit: courseLimit,
});
if (newUserCourseList.length === courseLimit) {
newComponentFamilyList.push(component.component_name);
}
});
newCheckedComponentFamilyList.push({
checkedComponentFamily: newComponentFamilyList,
limit: componentFamilyLimit,
});
});
setComponentLimit(newCheckedComponentList);
setComponentFamilyLimit(newCheckedComponentFamilyList);
setIsLoadingData(false);
}
})
.catch((e) => {
console.log(e);
});
};
const renderCourseRequirements = (
requiredCourses,
componentIndex,
componentFamilyIndex,
componentFamilyName
) => {
return (
<Grid container spacing={1} key={[uuid()]}>
{requiredCourses.map((course, index) => {
return (
<Grid key={[uuid()]} item xs={6} lg={4} xl={4}>
<Card
key={[uuid()]}
className={
checkedCourses.includes(course)
? "CourseCardCompleted"
: "CourseCard"
}
variant="outlined"
>
<CardHeader
key={[uuid()]}
action={
<Checkbox
name={course}
value={course}
className="header"
disabled={isDisabled(
course,
componentIndex,
componentFamilyIndex
)}
checked={checkedCourses.includes(course)}
onChange={() =>
handleOnChange(
course,
componentFamilyName,
componentIndex,
componentFamilyIndex
)
}
/>
}
title={
<Typography
key={[uuid()]}
className="CourseTitle"
color="textSecondary"
gutterBottom
>
{course}
</Typography>
}
></CardHeader>
<CardContent key={[uuid()]} className="content"></CardContent>
</Card>
</Grid>
);
})}
</Grid>
);
};
var componentCount = 0;
const renderComponents = (components, componentFamilyIndex) => {
return (
<Grid
container
spacing={1}
direction="column"
alignItems="center"
justifyContent="center"
key={[uuid()]}
>
{components.map((component, index) => {
var temp_index = componentCount;
componentCount = componentCount + 1;
return (
<Grid key={[uuid()]} item className="grid" xs={12} lg={4} xl={2}>
<Card
key={[uuid()]}
className={
componentLimit[temp_index].checked.length >=
componentLimit[temp_index].limit
? "ComponentCardCompleted"
: "ComponentCard"
}
variant="outlined"
>
<CardContent key={[uuid()]}>
<Typography
key={[uuid()]}
className="ComponentTitle"
color="textSecondary"
gutterBottom
>
{component.component_name}
</Typography>
<Typography
key={[uuid()]}
className="pos"
color="textSecondary"
>
Required # of Courses: {component.required_num_courses}
</Typography>
{renderCourseRequirements(
component.course_list,
temp_index,
componentFamilyIndex,
component.component_name
)}
</CardContent>
</Card>
</Grid>
);
})}
</Grid>
);
};
const renderComponentFamilies = (majorComponentFamilies) => {
return (
<Masonry className="masonry" columns={3} spacing={2} key={[uuid()]}>
{majorComponentFamilies.map((componentFamily, index) => {
if (componentFamily.component_list.length > 1)
return (
<Card
key={[uuid()]}
className={
componentFamilyLimit[index].checkedComponentFamily.length >=
componentFamilyLimit[index].limit
? "ComponentFamilyCardCompleted"
: "ComponentFamilyCard"
}
variant="outlined"
>
<CardContent key={[uuid()]}>
<Typography
key={[uuid()]}
className="ComponentFamilyTitle"
color="textSecondary"
gutterBottom
>
{componentFamily.component_family_name}
</Typography>
<Typography
key={[uuid()]}
className="pos"
color="textSecondary"
>
Required # of Components :{" "}
{componentFamily.required_num_components}
</Typography>
{renderComponents(componentFamily.component_list, index)}
</CardContent>
</Card>
);
return (
<div key={[uuid()]}>
{renderComponents(componentFamily.component_list, index)}
</div>
);
})}
</Masonry>
);
};
return (
<div className="container home">
<SearchBar
key={[uuid()]}
placeholder="Enter Major Name ..."
data={MajorNames}
onChange={(value) => getData(value)}
/>
<div className="mui-grid">
{Array.isArray(majorData.majorComponentFamilies) &&
majorData.majorComponentFamilies.length > 0 &&
!isLoadingData ? (
<>
<h1>{majorData.majorName} </h1>
{renderComponentFamilies(majorData.majorComponentFamilies)}
</>
) : (
<></>
)}
</div>
</div>
);
};

How can I take a value from an input tag in a TSX component, and use that value in a Node JS file in a different directory?

I'm working on a personal project where I'm pulling an API through Fetch; at the moment I can send the call from my index.js file to a TSX component that calls the API URL when my SearchButton component is clicked, but the search term needs to be declared in index.js.
Here's my SearchButton code (TSX):
import React, { useState } from 'react'
function SearchButton() {
const [ newsResponse, setNewsResponse ]= useState(null);
function queryOnClick() {
fetch(`http://localhost:4000/news-api`, {
headers: { 'Content-Type': 'application/json' }
})
.then((response) => response.json())
.then((result) => {
console.log('result:', result);
setNewsResponse(result);
})
.catch((ex) => {
console.log('error:', ex);
});
}
return (
<div className="theme--white">
<button className="search__button padding-1 margin-1 margin-left-6" onClick={queryOnClick}>
Click to search
</button>
{newsResponse && newsResponse.articles ? (
<div className="results__container padding-2 theme--mist">
{newsResponse.articles.map((article: {
title: React.ReactNode;
author: string;
content: string;
url: string;
}) => (
<div className="article__container box-shadow padding-2 margin-4 margin-left-6 margin-right-6 theme--white">
<h2 className="article__title padding-bottom-2 margin-bottom-2">{article.title}</h2>
<h3 className="article__author padding-bottom-2 margin-bottom-2">Written by: {article.author || 'An uncredited author'}</h3>
<p className="article__content">
{article.content.length > 150 ?
`${article.content.substring(0, 150)}... [Article shortened - Click the URL below to read more]` : article.content
}
</p>
<div className="article__url margin-top-2">
<p>
<p>Source:</p>
<a href={article.url}>{article.url}</a>
</p>
</div>
</div>
))}
</div>
) : null}
</div>
);
}
export default SearchButton;
I want to change that so a user can search for an article from the API by using a HTML input to submit a topic which would amend the API URL. For instance, if I searched Bitcoin, it would search https://API-${Bitcoin}.com. Due to CORS policy blocking, I can't just call the API in my TSX file as it has to go from localhost:3000 > localhost:4000 via the Node JS file.
At the moment, my input renders the user's query into the console, but I can't seem to get it over to my index.js file. How can I pass a value that's either in the console.log, or from the input's value, through to my Node JS index.js file?
Here's my SearchBar file that handles my Input (TSX):
import React, { Component } from 'react';
type SearchBarProps = {
searchNews: (text: string) => void;
}
type SearchBarState = {
searchString: string;
}
class SearchBar extends Component<SearchBarProps, SearchBarState> {
static defaultProps = {
searchNews: (text: string) => {}
}
state = {
searchString: ''
}
searchNews = (e: any) => {
const { searchString } = this.state
if(e.key === 'Enter' && searchString !== '') {
e.preventDefault();
e.stopPropagation();
this.props.searchNews(searchString)
console.log(searchString)
}
}
onSearchTextChange = (e: any) => {
this.setState({
searchString: e.target.value.trim()
})
}
render() {
return (
<div>
<form>
<div>
<input
id="search"
type="search"
value={this.state.searchString}
onChange={this.onSearchTextChange}
onKeyPress={e => this.searchNews(e)} placeholder="Search" />
</div>
</form>
</div>
);
}
}
export default SearchBar;
...And here's my index.js Node JS file (JS):
/*
* Libs
*/
const express = require('express');
const fetch = require('node-fetch');
const cors = require('cors');
const app = express();
/*
* Constants
*/
const PORT = 4000;
const API_KEY = 'x';
const SEARCH_QUERY = "Bitcoin";
const SORT_BY = "popularity";
const PAGE_SIZE = 10;
/*
* Setup CORS - This is needed to bypass NewsAPI CORS Policy Blocking by rerouting request to localhost
*/
const corsOptions = {
origin: 'http://localhost:3000',
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
/*
* Setup to request NewsAPI data using Fetch API
*/
app.get('/news-api', function (req, res) {
fetch(`https://newsapi.org/v2/everything?q=${SEARCH_QUERY}&sortBy=${SORT_BY}&pageSize=${PAGE_SIZE}&apiKey=${API_KEY}`, {
headers: { 'Content-Type': 'application/json' }
})
.then((response) => response.json())
.then((result) => {
console.log('result:', result);
res.json(result);
})
.catch((ex) => {
console.log('error:', ex);
res.status(400).send({
message: 'This is an error!',
error: ex
});
});
})
/*
* Start Backend API Proxy server
*/
app.listen(PORT, () => {
console.log(`=================`)
console.log(`API Connected!`)
console.log(`Listening at http://localhost:${PORT}`)
console.log(`=================`)
})
TLDR:
I have a TSX component that is an input (A - value={this.state.searchString}).
I want that input's value to go to a Node JS file to append a URL via a const (B - const SEARCH_QUERY).
I know what to pull from A, and where to put it in B, but don't know how to do so.
Full tech stack
Using Fetch API, React, TypeScript, Node JS and Webpack.
File paths
SearchButton: project/frontend/src/components/SearchButton/SearchButton.tsx
SearchBar: project/frontend/src/components/SearchBar/SearchBar.tsx
Node JS handler: project/backend/index.js
Essentially what you are asking here is how to pass data from the frontend to the backend. The way to do this is by including the user's search term in your fetch request to the backend. You can either include it in the body of a POST request or include it as a query string in the URL. You would need to use the body for passing large amounts of data, but something as simple as a search term can be done with a query string.
Front End
Include the current search term as a query parameter of your fetch request. I am using encodeURIComponent to apply percent-encoding to special characters.
function queryOnClick() {
// applies percent-encoding to special characters
const search = encodeURIComponent(searchString);
const url = `http://localhost:4000/news-api?search=${search}`;
fetch(url, {
...
You are missing the communication between your SearchButton and SearchBar components. I am not sure where these two components are in relation to each other on your page. If they are siblings then you will need to lift the searchString state and the queryOnClick function up to a shared parent.
I rearranged all of your components so that you have access to the right state in the right places.
import React, { useState } from "react";
function SearchButton({ onClick }: { onClick: () => void }) {
return (
<button
className="search__button padding-1 margin-1 margin-left-6"
onClick={onClick}
>
Click to search
</button>
);
}
interface SearchBarProps {
searchNews: () => void;
searchString: string;
setSearchString: (s: string) => void;
}
function SearchBar({ searchNews, searchString, setSearchString }: SearchBarProps) {
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" && searchString !== "") {
e.preventDefault();
e.stopPropagation();
searchNews();
}
};
const onSearchTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchString(e.target.value.trim());
};
return (
<div>
<form>
<div>
<input
id="search"
type="search"
value={searchString}
onChange={onSearchTextChange}
onKeyPress={handleKeyPress}
placeholder="Search"
/>
</div>
</form>
</div>
);
}
interface Article {
title: string;
author: string;
content: string;
url: string;
}
interface NewsResponse {
articles: Article[];
}
function ArticleList({ articles }: NewsResponse) {
return (
<div className="results__container padding-2 theme--mist">
{articles.map((article) => (
<div className="article__container box-shadow padding-2 margin-4 margin-left-6 margin-right-6 theme--white">
<h2 className="article__title padding-bottom-2 margin-bottom-2">
{article.title}
</h2>
<h3 className="article__author padding-bottom-2 margin-bottom-2">
Written by: {article.author || "An uncredited author"}
</h3>
<p className="article__content">
{article.content.length > 150
? `${article.content.substring(
0,
150
)}... [Article shortened - Click the URL below to read more]`
: article.content}
</p>
<div className="article__url margin-top-2">
<p>
<p>Source:</p>
<a href={article.url}>{article.url}</a>
</p>
</div>
</div>
))}
</div>
);
}
function SearchPage() {
const [newsResponse, setNewsResponse] = useState<NewsResponse | null>(null);
const [searchString, setSearchString] = useState("");
function queryOnClick() {
// applies percent-encoding to special characters
const search = encodeURIComponent(searchString);
const url = `http://localhost:4000/news-api?search=${search}`;
fetch(url, {
headers: { "Content-Type": "application/json" }
})
.then((response) => response.json())
.then((result) => {
console.log("result:", result);
setNewsResponse(result);
})
.catch((ex) => {
console.log("error:", ex);
});
}
return (
<div className="theme--white">
<SearchBar
searchNews={queryOnClick}
searchString={searchString}
setSearchString={setSearchString}
/>
<SearchButton onClick={queryOnClick} />
{newsResponse && newsResponse.articles ? (
<ArticleList articles={newsResponse.articles} />
) : null}
</div>
);
}
export default SearchPage;
Back End
You need to access the search term from the search parameter of the request URL. We use the req.params property to get a dictionary of params. We can use your previous search term "Bitcoin" as the default value if there was no search param on the request.
I'm not certain if we need to encode again here or not -- you'll want to play with that.
app.get('/news-api', function (req, res) {
const searchQuery = req.params.search || "Bitcoin";
fetch(`https://newsapi.org/v2/everything?q=${searchQuery}&sortBy=${SORT_BY}&pageSize=${PAGE_SIZE}&apiKey=${API_KEY}`, {
...

onEndReached FlatList React Native

i currently have a FlatList in my React Native project that renders the posts from nodejs backend. I have applied the pagination to my backend and it is working when i am testing it on postman. My problem is when i test my app on a simulator when i reach the end of the page 0, page 1 does not load.
Here is my FlatList:
<FlatList
data={posts} // to have all the data
keyExtractor={(post) => post.id.toString()}
renderItem={({ item }) => (
<PostCard
title={item.title}
subTitle={item.subTitle}
/>
)}
onEndReached={onScrollHandler}
onEndThreshold={0}
/>
const { data: posts, error, loading, request: loadPosts } = useApi(
postsApi.getPosts
);
useEffect(() => {
loadPosts();
}, []);
const[page,setPage]=useState(0);
const[profiles,setProfiles]=useState([]);
const fetchRecords=(page)=>{
const newRecords = []
for(var i = page * 10, il = i + 10; i < il && i < posts.length; i++){
newRecords.push(posts[i]);
}
setProfiles(...profiles, ...newRecords)
}
const onScrollHandler =()=>{
setPage(page+1);
fetchRecords(page)
}
Here is my nodeJS backend:
router.get("/",
async (req, res) => {
const getPagination = (page, size) => {
const limit = size ? +size : 10;
const offset = page ? page * limit : 0;
return { limit, offset };
};
const { page, size } = req.query;
const { limit, offset } = getPagination(page, size);
const posts = await Post.findAll({
limit,offset,
Here my route is /posts which i declared in my index.js in my backend.
Before applying pagination in my backend, when i used to do console.log(posts) in frontend i used to get all my posts but after i applied pagination when i do console.log(posts) i only get the posts for the first page.
Can you try to put this in useEffect.
useEffect(()=>{ apiCall()},[page])
If I am not wrong, you want to concatenate newly fetched records with the records that you already have, if that is the case, then you are supposed to do it like below:
setProfiles([...profiles, ...newRecords])
example:
let str1 = [1,2,3,4]
let str2 = [5,6,7,7]
// you dont want that
console.log(...str1, ...str2)
// but this, it will create a new array,
// having elements of both str1 and str2 which is what should be
console.log([...str1, ...str2])
Example:
import React, { useState } from 'react';
import { Text, View, StyleSheet, FlatList } from 'react-native';
import Constants from 'expo-constants';
// You can import from local files
import AssetExample from './components/AssetExample';
// or any pure javascript modules available in npm
import { Card } from 'react-native-paper';
export default function App() {
const [posts, setPosts] = useState(
Array.from({ length: 10 }, () => Math.floor(Math.random() * 100))
);
const onEndReached = () => {
let newPosts = Array.from(
{ length: 4 },
() => 'New Item Added ' + Math.floor(Math.random() * 100)
);
console.log(newPosts)
setTimeout(() => {
setPosts([...posts, ...newPosts]);
}, 1000);
};
return (
<View style={styles.container}>
<FlatList
data={posts}
onEndReached={onEndReached}
keyExtractor={(posts) => Math.floor(Math.random() * 1000)}
renderItem={({ item }) => (
<Card style={styles.card}>
<Text style={styles.paragraph}>{item}</Text>
</Card>
)}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
card: {
margin: 10,
padding: 10,
},
paragraph: {
margin: 24,
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
});
Expo Demo
Screenshow:
Did you inspect if your onScrollHandler function is called whenever you reached at the end of the flat list.
Also please wrap your flat list inside SafeAreaView and give SafeAreaView style of flex : 1
<SafeAreaView>
<FlatList/>
</SafeAreaView>

How can I modify this code so that render waits for Promise Items?

I am new to React and I load all the data from my database initially on page load but there is info I need to find in an array and apparently it isn't instant. What do I need to do to make sure the render method only renders the objects when the object promises have resolved?
I haven't tried much... I'm really stuck here.
This seems different than the other problems I've read here because I load a bunch on info in the beginning just fine but I need to call some team information every time a function is called so it isn't as simple as loading it once because the object i need is always different.
This code is the main issue. I also included the full file below:
I did some modification to the code in a edit: I realized that I just need to call the opponent team because I have the player team already.
if (team.id === game.team_1) {
var redTeam = team;
// set blueTeam based on game.team_1
// firebase.teams().doc('teams/{game.team_2}')
} else {
var blueTeam = team;
// set redTeam based on game.team_1
// firebase.teams().doc('teams/{game.team_1}')
}
Full file:
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import Async from 'react-promise'
import { withFirebase } from '../Firebase';
// import * as ROUTES from '../../constants/routes';
import { Container, Image, Spinner, Col, Row, Card, Accordion, Button } from 'react-bootstrap'
class PlayerGameList extends Component {
constructor(props) {
super(props);
this.state = {
loadingTeams: false,
loadingSchedule: false,
teams: [],
schedule: []
};
}
componentDidMount() {
this.setState({
loadingTeams: true,
loadingSchedule: true,
});
this.unsubscribe = this.props.firebase
.teams()
.where('players', 'array-contains', '-LXkkB7GNvYrU4UkUMle')
.onSnapshot(snapshot => {
let teams = [];
snapshot.forEach(doc =>
teams.push({ ...doc.data(), uid: doc.id }),
);
this.setState({
teams,
loadingTeams: false,
});
});
this.unsubscribe2 = this.props.firebase
.schedule()
.onSnapshot(snap => {
let schedule = [];
snap.forEach(doc =>
schedule.push({ ...doc.data(), uid: doc.id }),
);
this.setState({
schedule,
loadingSchedule: false,
});
});
}
componentWillUnmount() {
this.unsubscribe();
this.unsubscribe2();
}
render() {
const { teams, schedule, loadingTeams, loadingSchedule } = this.state;
return (
<div>
<h2>Games</h2>
{loadingTeams && loadingSchedule && <div colSpan="12"><Spinner animation="border" role="status">
<span className="sr-only">Loading...</span>
</Spinner></div>}
{/* CONTENT */}
<Container fluid>
<Row>
{getTeams({ teams, schedule })}
</Row>
</Container>
</div >
);
}
}
function getTeams({ teams, schedule }) {
if (!teams) {
return null;
}
if (!teams.length) {
return null;
} else {
return teams.map(team => getGames({ team, schedule }))
}
}
function getGames({ team, schedule }) {
schedule.sort((a, b) => (a.time > b.time) ? -1 : 1)
if (!schedule) {
return null;
}
if (!schedule.length) {
return null;
} else {
return schedule.map(game => guts({ team, game }));
}
}
function guts({ team, game }) {
const image = {
height: '25px',
width: '25px'
}
if (team.id === game.team_1) {
var redTeam = team;
// set blueTeam based on game.team_1
// firebase.teams().doc('teams/{game.team_2}')
} else {
var blueTeam = team;
// set redTeam based on game.team_1
// firebase.teams().doc('teams/{game.team_1}')
}
if (game.team_1 === team.id || game.team_2 === team.id) {
var time = new Date(game.time.seconds * 1000);
var dateFormat = require('dateformat');
var finalTime = dateFormat(time, 'ddd mmm dd, h:MM tt')
return (
<Col lg='4' md='6' sm='12' key={game.uid} style={{ marginBottom: '15px' }}>
<Card>
<Card.Body>
<Row>
<Image src={team.logo} style={image} roundedCircle />
<p>{team.name}</p>
<div style={{ height: '25px', width: '25px', backgroundColor: 'red' }}></div>
</Row>
<Row>
<Image src={team.logo} style={image} roundedCircle />
<p>{team.name}</p>
<div style={{ height: '25px', width: '25px', backgroundColor: 'blue' }}></div>
</Row>
<Row>
<div>
{finalTime}
</div>
</Row>
</Card.Body>
<Accordion>
<Card style={{ margin: '0', padding: '0' }}>
<Card.Header>
<Accordion.Toggle as={Button} variant="link" eventKey="0">
Show Match IDs
</Accordion.Toggle>
</Card.Header>
<Accordion.Collapse eventKey="0">
<Card.Body>{game.match_id}</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
</Card>
</Col>
);
}
}
export default withFirebase(PlayerGameList);
The items all load blank then a few seconds later all the console logs come through with the array objects. When I tell it to await the program just throws an error.

How to paginate a list of posts belonging to a specific category in GatsbyJS

I have developed a blog with Gatsby JS and I managed to add categories to each markdown file so that I can create pages by querying a specific category and list all the posts related to that category.
Now, I'm trying to add pagination to avoid an infinite list of posts inside each category page.
I have been following the official guide here: https://www.gatsbyjs.org/docs/adding-pagination/
And this is the code I came up with:
gatsby-node.js
const path = require('path')
const _ = require("lodash")
const { createFilePath } = require("gatsby-source-filesystem")
exports.createPages = ({actions, graphql}) => {
const {createPage} = actions
const articleTemplate = path.resolve(`src/templates/article.js`)
const categoryTemplate = path.resolve("src/templates/category.js")
return new Promise((resolve, reject) => {
resolve(
graphql(
`
{
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date] }
limit: 2000
) {
edges {
node {
html
id
frontmatter {
path
title
categories
}
}
}
}
}
`).then(result => {
if (result.errors) {
reject(result.errors)
}
const articles = result.data.allMarkdownRemark.edges
const articlesPerPage = 6
const numPages = Math.ceil(articles.length / articlesPerPage)
//Creating a page for each article
articles.forEach(({ node }) => {
createPage({
path: node.frontmatter.path,
component: articleTemplate,
//context: {}, // additional data can be passed via context
})
})
// Categories pages:
let categories = []
// Iterate through each article, putting all found categories into `categories`
_.each(articles, edge => {
if (_.get(edge, "node.frontmatter.categories")) {
categories = categories.concat(edge.node.frontmatter.categories)
}
})
Array.from({ length: numPages }).forEach((category, _, i) => {
createPage({
path: i === 0 ? `/${_.kebabCase(category)}/` : `/${_.kebabCase(category)}/${i + 1}`,
component: categoryTemplate,
context: {
limit: articlesPerPage,
skip: i * articlesPerPage,
category,
},
})
})
})
)
})
/templates/categories.js
import React from "react"
import PropTypes from "prop-types"
import Layout from '../layouts/layout'
import ArticleCard from '../components/articles/articlecard'
// Components
import { Link, graphql } from "gatsby"
const _ = require("lodash")
const Categories = ({ pageContext, data }) => {
const { category } = pageContext
const { edges } = data.allMarkdownRemark
return (
<Layout>
<section class="hero is-info is-medium has-text-centered">
<div class="hero-body">
<div class="container">
<h1 class="title is-top">
{category}
</h1>
</div>
</div>
</section>
<div class="section">
<div class="container">
<div class="columns is-multiline">
{edges.map(({ node }) => {
const { path, title, date } = node.frontmatter
return (
<div class="column is-half">
<div class="card">
<div class="card-header">
<p class="card-header-content">{date}</p>
</div>
<div class="card-content">
<Link to={_.kebabCase(category)}><span class="tag is-success has-padding">{category}</span></Link>
<Link to={path}>
<h2 class="title is-4">{title}</h2>
</Link>
</div>
<div class="card-footer">
<div class="card-footer-item"><Link to={path}><div class="button is-success is-inverted is-fullwidth">Read</div></Link></div>
<div class="card-footer-item"><Link to={path}><div class="button is-info is-inverted is-fullwidth">Share on Linkedin</div></Link></div>
</div>
</div>
</div>
)
})}
</div>
</div>
</div>
</Layout>
)
}
Categories.propTypes = {
pageContext: PropTypes.shape({
category: PropTypes.string.isRequired,
}),
data: PropTypes.shape({
allMarkdownRemark: PropTypes.shape({
totalCount: PropTypes.number.isRequired,
edges: PropTypes.arrayOf(
PropTypes.shape({
node: PropTypes.shape({
frontmatter: PropTypes.shape({
path: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
}),
}),
}).isRequired
),
}),
}),
}
export default Categories
export const pageQuery = graphql`
query($skip: Int!, $limit: Int!, $category: String) {
allMarkdownRemark(
sort: { fields: [frontmatter___date], order: DESC }
filter: { frontmatter: { categories: { in: [$category] } } }
limit: $limit
skip: $skip
) {
totalCount
edges {
node {
frontmatter {
title
path
date(formatString: "MMMM DD, YYYY")
}
}
}
}
}
`
This does not work and it is now throwing the error: error gatsby-node.js returned an error, TypeError: _.kebabCase is not a function
However kebabCase was used smoothly before modifying the query to add pagination, so I don't think the problem is actually there.
Does anyone have any clue?
Thank you!
You are declaring the variable "underscore" twice:
1- from the lodash library
2- from the forEach function:
Array.from({ length: numPages }).forEach((category, _, i)
just change the the second variable to another arbitrary name like this:
Array.from({ length: numPages }).forEach((category, otherName, i)

Resources