On page refresh it goes back to page 1 - pagination

I am building a blog with next.js and contentful. I am using pagination for blog posts. During development, if I am on page "/blog/?page=3" on page refresh it still shows the current page, but in production, if I refresh page it redirects to first page "/blog/?page=1"
Here is the code.
const Homepage = (props) => {
const router = useRouter();
const [page, updatePage] = useState(props.page);
const calculateRange = (length) => Array.from({ length }, (v, k) => k + 1);
const rangeLimit = Math.ceil(props.total / props.limit);
const range = calculateRange(rangeLimit);
useEffect(() => {
router.push({ pathname: "/blog", query: { page } });
}, [page]);
return (
<Layout SEO={seoConfig}>
<ContentfulPosts posts={props.entries} />
<div className="pagination">
<Paginator
handlePaginationChange={(event) => updatePage(event)}
range={range}
skip={page}
/>
</div>
</Layout>
);
};
Homepage.getInitialProps = async ({ query: { page = 1 } }) => {
if (page) {
page = parseInt(page);
}
const pglimit = 3;
const { entries, total, skip, limit } = await fetchEntries({
limit: pglimit,
skip: (page - 1) * pglimit,
});
return {
page,
entries,
total,
skip,
limit,
};
};
export default Homepage;
I dont know why it is working fine in development but not in production. How to solve it?

Related

React - fill datagrid with json fetch from node backend - TypeError null is not an object

I try to fill a React DataGrid with data from a JSON provided by node backend.
The backend code looks as follows:
app.get("/articles", (req, res) => {
res.json([
{
"title":"Test Article One",
"timestamp":"09-01-2023",
"text":"Test text one"
},
{
"title":"Test Article Two",
"timestamp":"10-01-2023",
"text":"Test text two"
},
{
"title":"Test Article Three",
"timestamp":"11-01-2023",
"text":"Test text three"
}])
})
The React code looks as follows:
const MemberPage = () => {
const [articles, setArticles] = useState([])
const [articleKeys, setArticleKeys] = useState([])
useEffect(() => {
fetch("http://localhost:3001/articles")
.then((res) => res.json())
.then((data) => {
setArticles(data)
setArticleKeys(Object.keys(data[0]))
})
})
return (
<div id="memberpage-main-container">
<DataGrid columns={articleKeys} rows={articles} />
</div>
)
}
I get the error message TypeError: null is not an object (evaluating 'measuringCell.getBoundingClientRect') in the browser console and the page wouldn't render. I first thought, it is because the DataGrid is rendered before the useEffect fetches the data which I've red in other answers, however, when I write:
const articleKeys = []
const articles = []
it works (I'm mean, it's an empty page then, but I don't get any errors). So, I would expect it not to be a problem when setting useState([]).
Any help is appreciated.
So, I've found a solution by switching from react-data-grid to #mui/x-data-grid.
The code now looks as follows:
import React, { useState, useEffect } from "react"
import { DataGrid } from "#mui/x-data-grid"
const MemberPage = () => {
const [articles, setArticles] = useState([])
const [articleKeys, setArticleKeys] = useState([])
function parseArticleKeys(keys) {
let tableColumns = []
for (let i = 0; i < keys.length; i++) {
tableColumns.push({field: keys[i], headerName: keys[i], width: 300})
}
return tableColumns
}
useEffect(() => {
fetch("http://localhost:3001/articles")
.then((res) => res.json())
.then((data) => {
setArticles(data)
setArticleKeys(Object.keys(data[0]))
})
})
return (
<div id="memberpage-news-container">
<DataGrid columns={parseArticleKeys(articleKeys)} rows={articles} />
</div>
)
}
export default MemberPage
I tried a similar thing with react-data-grid but couldn't get it to work. If someone has an idea to accomplish that with react-data-grid, it still might be helpful for others but I personally am ok with that solution.

Cannot import meta data from mdx file in getStaticProps nextjs

I have a problem while trying to require meta data from an mdx file in my Next.js project.
MDX file example:
export const meta = {
title: 'title',
date: new Date('May 09, 2019'),
};
Content
export const getStaticProps = async context => {
const postFilenames = await recRead(process.cwd() + '/pages', ['*.tsx']);
const postMetadata = await Promise.all(
postFilenames.map(async p => {
const { meta } = require(p);
return meta;
}),
);
return {
props: {
postMetadata: postMetadata,
},
};
};
It is a modified version of this: https://sarim.work/blog/dynamic-imports-mdx. While accessing a website I get an error:
Cannot find module '/home/oliwier/webDev/oliwierwpodrozy/pages/balkany/1.mdx'.
BTW recRead is this https://www.npmjs.com/package/recursive-readdir.
What is going on? Outside of getStaticProps I can import data.
I found something ridiculous when trying to solve the problem.
// 1)console.log(postFilenamesToImport[0]);
// 2) const meta = await import('../pages/wielka-brytania/1.mdx');
// 3) const meta = await import(postFilenamesToImport[0]);
// console.log(meta.meta);
shows: ../pages/wielka-brytania/1.mdx which is a string
This one works
But this one doesn't. Shows error: Error: Cannot find module '../pages/wielka-brytania/1.mdx'
It is not a const problem. It is written for tests and i know that using 2) and 3) together would cause problem. This error occurs when 1) is commented.
You can import metadata like follows.
First, we export the metadata from within the .mdx file
// in /pages/posts/example.mdx
import Layout from "../../components/layout";
export const meta = {
title: "example",
date: "2021-12-27",
slug: "example",
};
Lorem ipsum.
export default ({ children }) => (
<Layout subtitle={meta.title}>{children}</Layout>
);
Then we use getStaticProps to scan the file system at runtime, importing each .mdx file as a module, then mapping out their metadata exports. Since we are displaying the metadata on the index page, we will pop index.js from the array.
// in /pages/posts/index.js
export const getStaticProps = async (context) => {
const postDirectory = path.join(process.cwd(), "src/pages/posts");
let postFilenames = fs.readdirSync(postDirectory);
postFilenames = removeItemOnce(postFilenames, "index.js");
const postModules = await Promise.all(
postFilenames.map(async (p) => import(`./${p}`))
);
const postMetadata = postModules.map((m) => (m.meta ? m.meta : null));
return {
props: {
postMetadata: postMetadata,
},
};
// thanks https://sarim.work/blog/dynamic-imports-mdx
};
function removeItemOnce(arr, value) {
var index = arr.indexOf(value);
if (index > -1) {
arr.splice(index, 1);
}
return arr;
// thanks https://stackoverflow.com/a/5767357/13090245
}
Here is one way of using the prop to render a list of posts
// in /pages/posts/index.js
export default function PostsIndex({ postMetadata }) {
return (
<Layout subtitle="blog index">
<ul>
{postMetadata.map(({ slug, date, title }) => (
<li key={slug}>
<Link href={`/posts/${slug}`} a={title} />
<br />
{date}
</li>
))}
</ul>
</Layout>
);
}

Pagination in NextJs

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,
},
};
}

GSAP timeline needed on every page in Gatsby

My Gatsby site use the same GSAP timeline on every page, so I want to stay DRY and my idea is to include my timeline in my Layout component in that order.
But I don't know how to pass refs that I need between children and layout using forwardRef.
In short, I don't know how to handle the sectionsRef part between pages and layout.
sectionsRef is dependant of the page content (children) but is needed in the timeline living in layout.
How can I share sectionsRef between these two (I tried many things but always leading to errors)?
Here's a codesandbox without the refs in the Layout:
https://codesandbox.io/s/jolly-almeida-njt2e?file=/src/pages/index.js
And the sandbox with the refs in the layout:
https://codesandbox.io/s/pensive-varahamihira-tc45m?file=/src/pages/index.js
Here's a simplified version of my files :
Layout.js
export default function Layout({ children }) {
const containerRef = useRef(null);
const sectionsRef = useRef([]);
sectionsRef.current = [];
useEffect(() => {
gsap.registerPlugin(ScrollTrigger);
const scrollTimeline = gsap.timeline();
scrollTimeline.to(sectionsRef.current, {
x: () =>
`${-(
containerRef.current.scrollWidth -
document.documentElement.clientWidth
)}px`,
ease: 'none',
scrollTrigger: {
trigger: containerRef.current,
invalidateOnRefresh: true,
scrub: 0.5,
pin: true,
start: () => `top top`,
end: () =>
`+=${
containerRef.current.scrollWidth -
document.documentElement.clientWidth
}`,
},
});
}, [containerRef, sectionsRef]);
return (
<div className="slides-container" ref={containerRef}>
{children}
</div>
);
}
index.js (page)
import { graphql } from 'gatsby';
import React, { forwardRef } from 'react';
import SectionImage from '../components/sections/SectionImage';
import SectionIntro from '../components/sections/SectionIntro';
import SectionColumns from '../components/sections/SectionColumns';
const HomePage = ({ data: { home } }, sectionsRef) => {
const { sections } = home;
const addToRefs = (el) => {
if (el && !sectionsRef.current.includes(el)) {
sectionsRef.current.push(el);
}
};
return (
<>
{sections.map((section) => {
if (section.__typename === 'SanitySectionIntro') {
return (
<SectionIntro key={section.id} section={section} ref={addToRefs} />
);
}
if (section.__typename === 'SanitySectionImage') {
return (
<SectionImage key={section.id} section={section} ref={addToRefs} />
);
}
if (section.__typename === 'SanitySectionColumns') {
return (
<SectionColumns
key={section.id}
section={section}
ref={addToRefs}
/>
);
}
return '';
})}
</>
);
};
export default forwardRef(HomePage);
export const query = graphql`
query HomeQuery {
// ...
}
`;
Any clue greatly appreciated :)

React-Admin: Using "getList", I am getting "Error: cannot read property 'map' of undefined"

With react-admin, i'm trying to get the users list from API Restful Node server,and I have this error:
Error: cannot read property 'map' of undefined
Here's the getUsers in server user.controller.js:
const getUsers = catchAsync(async (req, res) => {
const users = await userService.getUsers(req.query);
const data = users.map(user => user.transform());
const total = users.length;
res.type('json');
res.set('Access-Control-Expose-Headers', 'Content-Range');
res.set('Content-Range', `users 0-2/${total}`);
res.set('X-Total-Count', total);
response = '{ data: ' + JSON.stringify(data) + ', total: ' + total + ' }';
res.send(response);
});
Here the data response received:
{
data: [
{"id":"5e6f5e3b4cf60a67701deeae","email":"admin#test.com","firstname":"Ad","lastname":"Min","role":"admin"},
{"id":"5e6f5e3b4cf60a67701deeaf","email":"test#test.com","firstname":"Jhon","lastname":"Doe","role":"user"}
],
total: 2
}
In react-admin, getList in dataProvider.js:
export default {
getList: (resource, params) => {
console.log(params);
const { field, order } = params.sort;
const query = {
...fetchUtils.flattenObject(params.filter),
sortBy: field
};
const url = `${apiUrl}/${resource}?${stringify(query)}`;
return httpClient(url).then(({ headers }, json) => ({
data: json,
total: parseInt(
headers
.get("Content-Range")
.split("/")
.pop(),
10
)
}));
},
Update:
UserList.tsx
import React from "react";
import {
TextField,
Datagrid,
DateInput,
Filter,
List,
EmailField,
SearchInput
} from "react-admin";
import { useMediaQuery, Theme } from "#material-ui/core";
import SegmentInput from "./SegmentInput";
import MobileGrid from "./MobileGrid";
const UserFilter = (props: any) => (
<Filter {...props}>
<SearchInput source="q" alwaysOn />
<DateInput source="createdAt" />
<SegmentInput />
</Filter>
);
const UserList = (props: any) => {
const isXsmall = useMediaQuery<Theme>(theme => theme.breakpoints.down("xs"));
return (
<List
{...props}
filters={<UserFilter />}
sort={{ field: "createdAt", order: "desc" }}
perPage={25}
>
{isXsmall ? (
<MobileGrid />
) : (
<Datagrid optimized rowClick="edit">
<TextField source="id" />
<EmailField source="email" />
<TextField source="firstname" />
<TextField source="lastname" />
<TextField source="role" />
<DateInput source="createdAt" />
<DateInput source="updatedAt" />
</Datagrid>
)}
</List>
);
};
export default UserList;
Here the documentation with somes examples for getList:
https://marmelab.com/react-admin/DataProviders.html#writing-your-own-data-provider
I don't understand, i need help please, what wrong?
Thanks & Regards
Ludo
Here's a detailed explanation of what's happening with the map() in reactjs.
And this source is on point for resolving this in nodejs
In your case, let's take a closer look here:
// user.controller.js
const getUsers = catchAsync(async (req, res) => {
// Actually, it might be wiser to first declare users
+ let users = [];
// You await for users (querying) to be resolved or rejected, cool
- const users = await userService.getUsers(req.query);
// And we then we assign "users" as before
+ users = await userService.getUsers(req.query);
// But here, map() requires users to be defined before iteration.
// So for this to work, we need the value of users to be resolved first, right?
- const data = users.map(user => user.transform());
// Could you change the above line to this, and see?
// This doesn't because we gave map(), a synchronous parameter (function)
- const data = Promise.all(users.map(user => user.transform()));
// Ok, break this up for easier understanding
// let's declare a userList, where we give map() an "async" parameter
// At this point, userList will be an "Array of Promises"
+ const userList = users.map(async user => user.transform());
// Now we resolve all the Promises, and we should have our clean data
+ const data = await Promise.all(userList);
});
With that change, we make sure that map() runs after our users is resolved.
Does that work for you? Let me know.

Resources