├── .eslintrc.json
├── next.config.js
├── public
├── favicon.ico
└── vercel.svg
├── pages
├── api
│ └── hello.js
├── _app.js
├── index.js
├── infiniteCSR.js
├── paginationCSR.js
└── paginationSSR.js
├── .gitignore
├── package.json
├── README.md
└── styles
├── globals.css
└── Home.module.css
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | reactStrictMode: true,
3 | }
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elisabeth-leonhardt/react-query-pagination/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default function handler(req, res) {
4 | res.status(200).json({ name: 'John Doe' })
5 | }
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pagination",
3 | "private": true,
4 | "scripts": {
5 | "dev": "next dev -p 3001",
6 | "build": "next build",
7 | "start": "next start",
8 | "lint": "next lint"
9 | },
10 | "dependencies": {
11 | "@material-ui/core": "^4.12.3",
12 | "@material-ui/lab": "^4.0.0-alpha.60",
13 | "next": "12.0.4",
14 | "react": "17.0.2",
15 | "react-dom": "17.0.2",
16 | "react-infinite-scroll-component": "^6.1.0",
17 | "react-query": "^3.33.5"
18 | },
19 | "devDependencies": {
20 | "eslint": "7.32.0",
21 | "eslint-config-next": "12.0.4"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import "../styles/globals.css";
2 | import { ReactQueryDevtools } from "react-query/devtools";
3 |
4 | import { Hydrate, QueryClient, QueryClientProvider } from "react-query";
5 | import { useState } from "react";
6 |
7 | function MyApp({ Component, pageProps }) {
8 | const [queryClient] = useState(() => new QueryClient());
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 |
19 | export default MyApp;
20 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | Install the dependencies (I use npm but use whatever you like here)
6 |
7 | ```bash
8 | npm install
9 | ```
10 |
11 | Run the project
12 |
13 | ```bash
14 | npm run dev
15 | # or
16 | yarn dev
17 | ```
18 |
19 | Open [http://localhost:3001](http://localhost:3001) with your browser to see the result.
20 |
21 | ## This project implements Pagination and Infinite Scroll with the Rick and Morty API
22 |
23 | to see the implemented pagination with CSR, go to [http://localhost:3001/paginationCSR](http://localhost:3001/paginationCSR)
24 |
25 | to see the implemented pagination with SSR, go to [http://localhost:3001/paginationSSR](http://localhost:3001/paginationSSR)
26 |
27 | to see the implemented infinite scroll (CSR), go to [http://localhost:3001/infiniteCSR](http://localhost:3001/infiniteCSR)
28 |
29 | don't forget to check out my [blog](https://dev.to/elisabethleonhardt) on dev.to to read the explainations on how this is implemented!
30 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 |
3 | export default function Home(props) {
4 | return (
5 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | * {
10 | box-sizing: border-box;
11 | }
12 |
13 | h1 {
14 | text-align: center;
15 | }
16 |
17 | h2 {
18 | padding-top: 1rem;
19 | }
20 |
21 | #welcome {
22 | max-width: 1300px;
23 | margin: auto;
24 | }
25 |
26 | #welcome a {
27 | border: 2px solid black;
28 | border-radius: 10px;
29 | padding: 1rem;
30 | margin-right: 1rem;
31 | color: black;
32 | text-decoration: none;
33 | }
34 | .grid-container {
35 | display: grid;
36 | grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr));
37 | gap: 2rem;
38 | max-width: 1300px;
39 | width: 100%;
40 | margin: auto;
41 | padding: 2rem;
42 | }
43 |
44 | article {
45 | display: flex;
46 | flex-direction: column;
47 | align-items: center;
48 | text-align: center;
49 | border-radius: 0.5em;
50 | overflow: hidden;
51 | box-shadow: rgba(99, 99, 99, 0.5) 0px 2px 8px 0px;
52 | padding-bottom: 1rem;
53 | }
54 |
55 | article > *:not(img) {
56 | padding-inline: 1rem;
57 | }
58 |
59 | article img {
60 | object-fit: cover;
61 | }
62 |
63 | .pagination {
64 | max-width: 1300px;
65 | width: 100%;
66 | margin: auto;
67 | justify-content: center;
68 | display: flex;
69 | }
70 |
71 | .pagination:nth-of-type(2) {
72 | margin-bottom: 2rem;
73 | }
74 |
--------------------------------------------------------------------------------
/pages/infiniteCSR.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import InfiniteScroll from "react-infinite-scroll-component";
3 | import { useInfiniteQuery } from "react-query";
4 |
5 | function InfiniteCSRPage() {
6 | const { data, status, fetchNextPage, hasNextPage } = useInfiniteQuery(
7 | "infiniteCharacters",
8 | async ({ pageParam = 1 }) =>
9 | await fetch(
10 | `https://rickandmortyapi.com/api/character/?page=${pageParam}`
11 | ).then((result) => result.json()),
12 | {
13 | getNextPageParam: (lastPage, pages) => {
14 | if (lastPage.info.next) {
15 | return pages.length + 1;
16 | }
17 | },
18 | }
19 | );
20 | return (
21 |
22 |
23 | Rick and Morty with React Query and Infinite Scroll - Client Side
24 | Rendered
25 |
26 | {status === "success" && (
27 |
Loading...}
32 | >
33 |
34 | {data?.pages.map((page) => (
35 | <>
36 | {page.results.map((character) => (
37 |
38 |
45 |
46 |
Name: {character.name}
47 |
Lives in: {character.location.name}
48 |
Species: {character.species}
49 |
Id: {character.id}
50 |
51 |
52 | ))}
53 | >
54 | ))}
55 |
56 |
57 | )}
58 |
59 | );
60 | }
61 |
62 | export default InfiniteCSRPage;
63 |
--------------------------------------------------------------------------------
/pages/paginationCSR.js:
--------------------------------------------------------------------------------
1 | import { useQuery } from "react-query";
2 | import Pagination from "@material-ui/lab/Pagination";
3 | import { useEffect, useState } from "react";
4 | import { useRouter } from "next/router";
5 |
6 | export default function PaginationPage(props) {
7 | const router = useRouter();
8 | const [page, setPage] = useState(1);
9 | const { data } = useQuery(
10 | ["characters", page],
11 | async () =>
12 | await fetch(
13 | `https://rickandmortyapi.com/api/character/?page=${page}`
14 | ).then((result) => result.json()),
15 | {
16 | keepPreviousData: true,
17 | }
18 | );
19 | function handlePaginationChange(e, value) {
20 | setPage(value);
21 | router.push(`paginationCSR/?page=${value}`, undefined, { shallow: true });
22 | }
23 | useEffect(() => {
24 | if (router.query.page) {
25 | setPage(parseInt(router.query.page));
26 | }
27 | }, [router.query.page]);
28 | return (
29 |
30 |
31 | Rick and Morty with React Query and Pagination - Client Side Rendered
32 |
33 |
41 |
42 | {data?.results?.map((character) => (
43 |
44 |
51 |
52 |
Name: {character.name}
53 |
Lives in: {character.location.name}
54 |
Species: {character.species}
55 |
Id: {character.id}
56 |
57 |
58 | ))}
59 |
60 |
68 |
69 | );
70 | }
71 |
--------------------------------------------------------------------------------
/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | padding: 0 2rem;
3 | }
4 |
5 | .main {
6 | min-height: 100vh;
7 | padding: 4rem 0;
8 | flex: 1;
9 | display: flex;
10 | flex-direction: column;
11 | justify-content: center;
12 | align-items: center;
13 | }
14 |
15 | .footer {
16 | display: flex;
17 | flex: 1;
18 | padding: 2rem 0;
19 | border-top: 1px solid #eaeaea;
20 | justify-content: center;
21 | align-items: center;
22 | }
23 |
24 | .footer a {
25 | display: flex;
26 | justify-content: center;
27 | align-items: center;
28 | flex-grow: 1;
29 | }
30 |
31 | .title a {
32 | color: #0070f3;
33 | text-decoration: none;
34 | }
35 |
36 | .title a:hover,
37 | .title a:focus,
38 | .title a:active {
39 | text-decoration: underline;
40 | }
41 |
42 | .title {
43 | margin: 0;
44 | line-height: 1.15;
45 | font-size: 4rem;
46 | }
47 |
48 | .title,
49 | .description {
50 | text-align: center;
51 | }
52 |
53 | .description {
54 | margin: 4rem 0;
55 | line-height: 1.5;
56 | font-size: 1.5rem;
57 | }
58 |
59 | .code {
60 | background: #fafafa;
61 | border-radius: 5px;
62 | padding: 0.75rem;
63 | font-size: 1.1rem;
64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
65 | Bitstream Vera Sans Mono, Courier New, monospace;
66 | }
67 |
68 | .grid {
69 | display: flex;
70 | align-items: center;
71 | justify-content: center;
72 | flex-wrap: wrap;
73 | max-width: 800px;
74 | }
75 |
76 | .card {
77 | margin: 1rem;
78 | padding: 1.5rem;
79 | text-align: left;
80 | color: inherit;
81 | text-decoration: none;
82 | border: 1px solid #eaeaea;
83 | border-radius: 10px;
84 | transition: color 0.15s ease, border-color 0.15s ease;
85 | max-width: 300px;
86 | }
87 |
88 | .card:hover,
89 | .card:focus,
90 | .card:active {
91 | color: #0070f3;
92 | border-color: #0070f3;
93 | }
94 |
95 | .card h2 {
96 | margin: 0 0 1rem 0;
97 | font-size: 1.5rem;
98 | }
99 |
100 | .card p {
101 | margin: 0;
102 | font-size: 1.25rem;
103 | line-height: 1.5;
104 | }
105 |
106 | .logo {
107 | height: 1em;
108 | margin-left: 0.5rem;
109 | }
110 |
111 | @media (max-width: 600px) {
112 | .grid {
113 | width: 100%;
114 | flex-direction: column;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/pages/paginationSSR.js:
--------------------------------------------------------------------------------
1 | import { useQuery, dehydrate, QueryClient } from "react-query";
2 | import Pagination from "@material-ui/lab/Pagination";
3 | import { useState } from "react";
4 | import { useRouter } from "next/router";
5 |
6 | export default function paginationSSR(props) {
7 | const router = useRouter();
8 | const [page, setPage] = useState(parseInt(router.query.page) || 1);
9 | const { data } = useQuery(
10 | ["characters", page],
11 | async () =>
12 | await fetch(
13 | `https://rickandmortyapi.com/api/character/?page=${page}`
14 | ).then((result) => result.json()),
15 | {
16 | keepPreviousData: true,
17 | refetchOnMount: false,
18 | refetchOnWindowFocus: false,
19 | }
20 | );
21 | function handlePaginationChange(e, value) {
22 | setPage(value);
23 | router.push(`paginationSSR/?page=${value}`, undefined, { shallow: true });
24 | }
25 | return (
26 |
27 |
28 | Rick and Morty with React Query and Pagination - Server Side rendered
29 |
30 |
38 |
39 | {data?.results?.map((character) => (
40 |
41 |
48 |
49 |
Name: {character.name}
50 |
Lives in: {character.location.name}
51 |
Species: {character.species}
52 |
Id: {character.id}
53 |
54 |
55 | ))}
56 |
57 |
65 |
66 | );
67 | }
68 |
69 | export async function getServerSideProps(context) {
70 | let page = 1;
71 | if (context.query.page) {
72 | page = parseInt(context.query.page);
73 | }
74 | const queryClient = new QueryClient();
75 | await queryClient.prefetchQuery(
76 | ["characters", page],
77 | async () =>
78 | await fetch(
79 | `https://rickandmortyapi.com/api/character/?page=${page}`
80 | ).then((result) => result.json())
81 | );
82 | return { props: { dehydratedState: dehydrate(queryClient) } };
83 | }
84 |
--------------------------------------------------------------------------------