Which one is better for pagination, fetchMore or getServerSideProps? - pagination

I want to crate a shopping online website.It have product pagination fuction.
In the docs of Nextjs, they recommend use getStaticProps and getStaticPaths than other(fetchMore in getStaticProps), but when I search Nextjs pagination, almost docs or tutorial use getServerSideProps or getInitialProps.
i tried to use with fetchMore and i don't see any problem,so why they use getServerSideProps?
My code
let paginationOptions: PaginationOptionsInput = {
skip: 0,
type: "SALES_DESC",
};
const Index = () => {
const [filterChecked,setFilterChecked] = useState("SALES_DESC")
const [currentPage,setCurrentPage] = useState(1)
const {data,fetchMore,loading} = useGetProductsQuery({
variables:{
paginationOptions
},
notifyOnNetworkStatusChange: true
})
console.log(data?.getProducts.products)
const handlePageChange = (page:number) =>{
setCurrentPage(page)
paginationOptions.skip = 4 * (page-1)
fetchMore({
variables:paginationOptions
})
}
return (
<div>
<Navbar />
<div className="distance">
...
{data?.getProducts.products &&
data.getProducts.products!.map((product) => (
<div className="col l-3 m-4 c-6" key={product.id}>
<div className={styles.productItem}>
<img src={product.thumbnail} />
<h2>{product.productName}</h2>
<h3>{product.priceToDisplay} VND</h3>
<div className={styles.paidInfo}>
<h4>{product.sales}</h4>
<h4>{product.commentAmount}</h4>
</div>
</div>
</div>
))}
<Pagination
className="pagination-bar"
currentPage={currentPage}
totalCount={data?.getProducts.totalCount!}
pageSize={data?.getProducts.pageSize!}
onPageChange={(page : number) => handlePageChange(page)}
/>
...
</div>
<Footer />
</div>
);
};
export const getStaticProps: GetStaticProps = async () => {
const { data,error,loading } = await client.query<GetProductsQuery>({
query: GetProductsDocument,
variables: { paginationOptions },
notifyOnNetworkStatusChange: true
});
return {
props: {
paginationProducts: data.getProducts,
},
};
};
export default Index;

Maybe I am missing something, but:
getInitialProps - to forget in nextjs v11+
getServerSideProps - every time page is called, [server-side]
getStaticProps and getStaticPaths - runs during build, [server-side]
I recommend you to take a look at this library [client-side]
When to use getServerSideProps(1) vs getStaticProps (2)
When you have pages where data needs to be loaded every time (example: settings, clients, etc).
When you have a lot of pages that need to be rendered, to reach huge performance. (example: product pages, some text pages - for example, "terms and conditions" with data from DB) where you know links previously. It's why in each getStaticProps page we need getStaticPaths.
SEO optimized pages are getStaticProps
So, if you have some rules for the pages, for example /product/1 /product/2 ... /product/n you need to create page with getStaticProps
Also, take a look at this

Related

Need to call an api for each key stroke in react, but the response can have thousands of objects

I am using react and axios for frontend, and nextjs with prisma for backend. I have in the database 4000 exercices that contain fitness exercices. I want to create a function where by each key stroke, the api will look for the relevant exercice. I finished creating it, but i have some issues:
The main problem is that the response is delayed from the first keystrokes, because the payload response is tooo large. I created a scrollable UL element to render the elements, because I want to get also the Gif images. So the elements, if the API will find those, will be rendered on the screen.
If I add to each element an on click event, to select the exercice's Id, I get an error "too many re-rendering on the screen".
How can I optimise the function, and how can I solve the error of too many re-render on the screen? Nextjs tells me that it will create an infinite loop....
The frontend looks like this:
const [open, setOpen] = useState(false);
const [keyWord, setKeyWord] = useState('');
const [array, setArray] = useState([]);
const [exerciceId, setExerciceId] = useState('');
// Add exercice
const hadnleAddExercie = async event => {
event.preventDefault();
console.log('exercice added');
}
// Look for exercices
const searchExercices = async event => {
event.preventDefault();
setKeyWord(event.target.value);
const arrayExercices = await getExercicesByKeyWords(keyWord);
setArray(arrayExercices);
console.log(arrayExercices);
}
<div className='flex mt-3 flex-col'>
<input onChange={searchExercices} required placeholder='Search by word...' className='border border-slate-400 p-1 rounded-md flex-1 max-w-sm my-2'/>
<ul className='border border-slate-400 p-1 rounded-md max-w-sm my-2 max-h-52 overflow-scroll'>
{
array.length > 1 && array.map(exercice => (
<li key={exercice.id} className='flex flex-wrap p-2 bg-slate-200 m-2 items-center rounded-md'>
<span><Image className='rounded-xl mr-2' priority width={40} height={40} src={exercice.gifUrl} alt={exercice.name}/></span>
<span>{ exercice.name }</span>
</li>
))
}
</ul>
</div>
The backend Uses prisma and I use the OR clause to look for a word in different rows:
export default async function handler(req, res) {
try {
const param = req.query.slug[0];
console.log(param);
// Get exercices where the two rows contains a single parametter
const exercices = await prisma.exercices.findMany({
where: {
OR: [
{
name: {
contains: param
}
},
{
target: {
contains: param
}
},
{
equipment: {
contains: param
}
}
]
}
});
res.status(200).send(exercices);
}
catch (error) {
console.log(error);
res.status(500).send(error);
}
}
An example can be this:
Only for finding an exercice I used 500mb...
Here are a few ways I can think of to optimize this:
Use pagination and fetch more results as user scrolls down or actually separate it by using pages. You can read more on how to implement pagination in Prisma here.
Add debounce to your search term so it doesn't actually fire on every single keystroke, you could use something like useDebounce.
Use React.memo to prevent the list from being re-rendered every time some state changes, only re-render it when the actual list changes.

How to prevent re-rendering when switching the pages in SolidJS?

I'm struggling with the re-rendering issue in the SolidJS application. I have two routes, Home and Detail. A user can explore items in Home, and click the link on the item name to switch a page to Detail to check out detailed information.
export default function Home() {
const [items, setItems] = createSignal<Item[]>([]);
onMount(async () => {
setItems(
await fetchItemsThroughExpensiveAPI()
);
});
return (
<main>
<For each={items()}>
{(item) => (
<A href={`/item/${item.id}`}>{item.name}</A>
)}
</For>
</main>
);
}
export default function Detail() {
const params = useParams<{ id: string }>();
return (
<main>
// Some detailed information for the item ...
</main>
);
}
At this point, the API(fetchItemsThroughExpensiveAPI) will be called back when the user returns to the Home from Detail. I'm expecting this it is caused by re-rendering. How do I prevent re-rendering Home whenever a user returns to Home from another page to avoid unnecessary API calls?
Use a resource to fetch the data outside the Home component. If you need to fetch the data once during application's life, cache it.
https://www.solidjs.com/docs/latest/api#createresource
Lets make it more clear. There are different patterns to render async data, data that resides in a remote location.
Fetch as you render: In this pattern, data is fetched when the component mounts.
ComponentA below uses this pattern. Whenever it is re-rendered, data will be re-fetched.
Fetch then render: In this pattern, data is fetched outside the component, in one of its parent's scope. When component mounts it can use whatever is currently available, by whatever, I mean the request may not be resolved yet so state will be pending.
Resource API is build to make use of this pattern.
ComponentB below uses this pattern. Since data is fetched outside the component, re-rendering has no effect on it.
import { Accessor, Component, createSignal, Match, Switch } from 'solid-js';
import { render } from 'solid-js/web';
interface State { status: 'pending' | 'resolved' | 'rejected', data?: any, error?: any };
function getData(): Accessor<State> {
const [state, setState] = createSignal<State>({ status: 'pending' });
setTimeout(() => {
setState({ status: 'resolved', data: { name: 'John Doe', age: 30 } });
}, 1000);
return state;
};
const ComponentA = () => {
const state = getData();
return (
<Switch fallback={<div>Not Found</div>}>
<Match when={state().status === 'pending'}>
Loading...
</Match>
<Match when={state().status === 'resolved'}>
{JSON.stringify(state().data)}
</Match>
<Match when={state().status === 'rejected'}>
{JSON.stringify(state().error)}
</Match>
</Switch>
);
};
const ComponentB: Component<{ state: Accessor<State> }> = (props) => {
return (
<Switch fallback={<div>Not Found</div>}>
<Match when={props.state().status === 'pending'}>
Loading...
</Match>
<Match when={props.state().status === 'resolved'}>
{JSON.stringify(props.state().data)}
</Match>
<Match when={props.state().status === 'rejected'}>
{JSON.stringify(props.state().error)}
</Match>
</Switch>
);
};
const App = () => {
const state = getData();
const [show, setShow] = createSignal(false);
const handleClick = () => setShow(prev => !prev);
return (
<div>
{show() && (<ComponentA />)}
{show() && (<ComponentB state={state} />)}
<div><button onclick={handleClick}>Toggle Show Components</button></div>
</div>
)
};
render(() => <App />, document.body);
Here you can see it in action: https://playground.solidjs.com/anonymous/32518df5-9840-48ea-bc03-87f26fecc0f4
Here we simulated an async request using setTimeout. This is very crude implementation to prove a point. If you are going to fetch a remote resource, you should use the Resource API which provides several utilities like automatic re-fecthing when request parameters change.
There are few issues with your implementation. First and foremost, async data in never guaranteed to be received, so you should handle failures.
Since it takes some time to receive the remote data, you have to show the user some indicator of the ongoing request, like a loader.
If API call is an expensive operation, you have to cache the result, rather than forcing the UI not to re-render. In this sense, your approach is very problematic.
Utilize the useEffect hook. When the component is first loaded, the useEffect hook can be used to retrieve the items from the API and store them in a state variable. This way, when the component re-renders, it can access the stored items in the state variable instead of making a new API call.
export default function Home() {
const [items, setItems] = useState<Item[]>([]);
useEffect(() => {
async function fetchData() {
setItems(await fetchItemsThroughExpensiveAPI());
}
fetchData();
}, []);
return (
<main>
<For each={items}>
{(item) => (
<A href={`/item/${item.id}`}>{item.name}</A>
)}
</For>
</main>
);
}

How should I fetch payment intent secret for Stripe Elements in my Next.js app?

I am trying to implement Stripe payments in my Next.js app as described in the guide here: https://stripe.com/docs/payments/quickstart
The guide tells me that in order to use Stripe Elements for my checkout form, I need to know payment intent. It says:
Create PaymentIntent as soon as the page loads
The issue is - our website will not have a separate payments page, the payment form will be displayed inside the modal, which is loaded on every page of the website. That means, I would have to fetch the payment intent for any user who ever visits any page on our website, whether they're planning to purchase the course or not, just so that I could display the payment form inside the modal. That doesn't seem right to me.
Can you give me some advice, let me know if there's a better way to handle this?
Another issue is that this guide tells me that I should pass the fetched payment intent clientSecret as an option to <Elements/> wrapper.
And if I hover on <Elements/> wrapper in my VSCdoe, it tells me:
[...] Render an Elements provider at the root of your React app so that it is available everywhere you need it. [...]
So, does that mean I have to put <Elements/> wrapper into my _app.tsx file? And that means I'd have to fetch the payment intent clientSecret inside of the _app.tsx? So that my app would fetch payment intent secret any time any user ever loads any page on my website?
Again, this seems pretty weird, wouldn't it slow things down, add extra requests and loading time to all my pages, and create a whole bunch of payment intents that are never used?
Render the payment form in a modal in Layout.js and wrap the
entire project in the Layout component
place this code in _app.js
import React, { useEffect, useState } from "react"
import { loadStripe } from "#stripe/stripe-js"
import { Elements } from "#stripe/react-stripe-js"
import Layout from "../components/Layout"
import PaymentModalForm from "../components/PaymentModalForm"
const promise = loadStripe("pk_test_....")
// replace pk_test_... with your publishable key
const API_URL = "http://localhost:8000"
// replace API_URL with your backend server url
const App = ({ Component, pageProps }) => {
const [secret, setSecret] = useState(null)
useEffect(() => {
const fetchSecret = async () => {
const response = await fetch(`${API_URL}/create_intent`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
items: [{ id: 'adidas boost', quantity: 2}]
})
})
const { client_secret } = await response.json()
setSecret(clientSecret)
}
fetchSecret()
}, [])
const options = {
clientSecret: secret,
appearance: { theme: "stripe"}
}
return (
{secret && (
<Elements stripe={promise} options={options}>
<Layout>
<Component {...pageProps} />
</Layout>
</Elements>
)}
)
}
export default App
Then in your Layout.js, fill in this code
import PaymentModalForm from "../components/PaymentModalForm"
import React, { useEffect, useState } from "react"
const Layout = ({ children }) => {
const [showModal, setShowModal] = useState(false)
const handleClick = () => {
if (showModal) {
setShowModal(false)
} else {
setShowModal(true)
}
}
return (
<div>
<div className="container">
{children}
<button onClick={handleClick}>Show Payment Modal</button>
</div>
{showModal ? (
<div className="modal fade">
<div className="modal-dialog">
<div className="modal-content">
<PaymentModalForm />
</div>
</div>
</div>
) : ( null )}
</div>
)
}
export default Layout
There's more work to be done in PaymentModalForm.js

TypeError: Cannot read property 'count' of undefined

I am a novice MERN stack developer.
I am trying to calculate the number of pages for pagination. The info object prints in console.log. However, when I try to use it in the for loop I get an error.
Can someone please explain what's the React logic or flow behind this? I have had issues with this multiple times but, could fix it with conditional rendering. But, somehow I wasn't able to fix this and I don't seem to understand the logic of how the flow in react is.
App Component :
const App = () => {
const [episodes, setEpisodes] = useState({});
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [episodesPerPage, setEpisodesPerPage] = useState(10);
useEffect(() => {
const fetchEpisodes = async () => {
setLoading(true);
const res = await axios.get('https://rickandmortyapi.com/api/episode/');
setEpisodes(res.data);
setLoading(false);
};
fetchEpisodes();
}, []);
console.log(episodes.info);
return (
<div>
<div id='header'>
<h1>Rick & Morty</h1>
<h2>Episodes</h2>
</div>
<div>
<h3>All Episodes</h3>
<EpisodeList episodeList={episodes.results} loading={loading} />
<Pagenation info={episodes.info} />
</div>
</div>
);
};
export default App;
Pagenation Component:
const Pagenation = ({ info }) => {
const pageNumbers = [];
console.log(info);
for (let i = 1; i <= Math.ceil(info.count / 20); i++) {
pageNumbers.push(i);
}
return (
<nav aria-label='...'>
<ul class='pagination pagination-lg'>
{pageNumbers.map((number) => {
return (
<li class='page-item active' aria-current='page'>
<span class='page-link'>
{number}
<span class='sr-only'>(current)</span>
</span>
</li>
);
})}
</ul>
</nav>
);
};
Conditional rendering can be the solution here as well.
episodes is initially an empty object, so episodes.info is initially undefined. This means you cannot access a property on info without checking if it exists first because you know already that it will be undefined at the beginning.
A simple solution might look like this:
{episodes.info && <Pagenation info={episodes.info} />}
You could also move the conditional into the Pagenation component to be something like this:
if (info) {
for (let i = 1; i <= Math.ceil(info.count / 20); i++) {
pageNumbers.push(i);
}
}
Regardless of your strategy to avoid the error, the core of the issue is that you have data that is loaded after the component mounts. This means you need to account for that data being missing for at least one render.

Nextjs how to not unmount previous page when going to next page (to keep state)

we are using Nextjs in our web app.
We want to keep stack of pages where users visit to keep state of component on back navigation.
How should we do that?
I have tried https://github.com/exogen/next-modal-pages, but it calls getInitialProps of previous pages again on back.
Here's my solution with a custom _app.js
import React, { useRef, useEffect, memo } from 'react'
import { useRouter } from 'next/router'
const ROUTES_TO_RETAIN = ['/dashboard', '/top', '/recent', 'my-posts']
const App = ({ Component, pageProps }) => {
const router = useRouter()
const retainedComponents = useRef({})
const isRetainableRoute = ROUTES_TO_RETAIN.includes(router.asPath)
// Add Component to retainedComponents if we haven't got it already
if (isRetainableRoute && !retainedComponents.current[router.asPath]) {
const MemoComponent = memo(Component)
retainedComponents.current[router.asPath] = {
component: <MemoComponent {...pageProps} />,
scrollPos: 0
}
}
// Save the scroll position of current page before leaving
const handleRouteChangeStart = url => {
if (isRetainableRoute) {
retainedComponents.current[router.asPath].scrollPos = window.scrollY
}
}
// Save scroll position - requires an up-to-date router.asPath
useEffect(() => {
router.events.on('routeChangeStart', handleRouteChangeStart)
return () => {
router.events.off('routeChangeStart', handleRouteChangeStart)
}
}, [router.asPath])
// Scroll to the saved position when we load a retained component
useEffect(() => {
if (isRetainableRoute) {
window.scrollTo(0, retainedComponents.current[router.asPath].scrollPos)
}
}, [Component, pageProps])
return (
<div>
<div style={{ display: isRetainableRoute ? 'block' : 'none' }}>
{Object.entries(retainedComponents.current).map(([path, c]) => (
<div
key={path}
style={{ display: router.asPath === path ? 'block' : 'none' }}
>
{c.component}
</div>
))}
</div>
{!isRetainableRoute && <Component {...pageProps} />}
</div>
)
}
export default App
Gist - https://gist.github.com/GusRuss89/df05ea25310043fc38a5e2ba3cb0c016
You can't "save the state of the page by not un-mounting it" but you can save the state of your app in _app.js file, and the rebuild the previous page from it.
Check the redux example from next's repo.

Resources