{"resource":{"author":{"id":"h2so9H8jPMmgUKGghoNl","name":"Yomesh Gupta","username":"yomeshgupta"},"content":{"link":null,"difficulty":4,"domain":2,"type":1,"isInternal":true,"body":"In this blog post, we are going to see how can we add pagination to Remix using core web building blocks.\n\n## How does basic pagination work?\n\nThe idea behind pagination is that we only fetch the items of the current page where the value of the current page is tracked via the URL. For example --\n\n- [https://devtools.tech/questions/all/?page=1](https://devtools.tech/questions/all/?page=1)\n- [https://devtools.tech/questions/all/?page=2](https://devtools.tech/questions/all/?page=2)\n- [https://devtools.tech/questions/all/?page=3](https://devtools.tech/questions/all/?page=3)\n- [https://devtools.tech/questions/all/?page=4](https://devtools.tech/questions/all/?page=4)\n\nHere each page is rendered using SSR and showcases a particular section of the questions. This helps in creating shareable URLs that our end users can further use to share among themselves.\n\n## Data Fetching\n\nTo implement, we first need to understand how data fetching works in Remix.\n\n### Loader\n\nIn most simple terms, each route can define a `loader` function that is invoked on the server before rendering to provide data to the route. It handles all the `GET` requests for that route. If you are coming from the Next.js world then it is similar to `getServerSideProps`.\n\n```js\n// Route: /questions/all | File: questions.all.js\nimport { useLoaderData } from \"remix\";\nimport { getQuestions } from \"./data/questions.server.js\";\n\nexport const loader = async () => {\n  // Data fetching can happen here\n  // getQuestions talks to our API and returns an array of questions\n  const questions = await getQuestions();\n\n  return {\n    questions,\n  };\n};\n\nexport const QuestionsRoute = () => {\n  const { questions } = useLoaderData();\n\n  // can use questions here\n};\n```\n\nIn the above code snippet, we export the `loader` function where we make a call to our API to fetch all questions. We can now access the response from the loader in the component using a custom hook provided by Remix called `useLoaderData`.\n\n## Rendering Logic\n\nWe have data from the `loader` now we can render the list of questions.\n\n```js\nexport const QuestionsRoute = () => {\n  const { questions } = useLoaderData();\n\n  if (!questions || !questions.length) {\n    return <div>Empty...</div>;\n  }\n\n  return (\n    <ul>\n      {questions.map((question) => (\n        <li key={question.id}>{question.title}</li>\n      ))}\n    </ul>\n  );\n};\n```\n\n## Handling Navigation\n\nLet us assume that our loader data provides the value of the `currentPage`. We can use this data to show the `previous` and `next` buttons. There could be multiple ways to do this.\n\n1. Simple Link component\n\n```js\nimport { Link } from 'remix';\n\nexport const QuestionsRoute = () => {\n  const { questions, currentPage } = useLoaderData();\n  const pagination = useMemo(() => {\n    const previousPage = currentPage - 1 || 1;\n    const nextPage = currentPage + 1;\n\n    const pagination = {\n      previous: {\n        disabled: previousPage <= 1,\n        link: `/questions/all/?page=$${previousPage}`\n      },\n      next: {\n        disabled: questions && !questions.length, // or some empty state indicator\n        link: `/questions/all/?page=$${nextPage}`\n      }\n    }\n\n    return pagination;\n  }, [currentPage]);\n\n  ...\n\n  return (\n    <>\n      ...\n      <div className=\"navigation\">\n        <Link to={pagination.previous.link} className={pagination.previous.disabled ? 'disabled': ''}>\n          Previous\n        </Link>\n        <Link to={pagination.next.link} className={pagination.next.disabled ? 'disabled': ''}>\n          Next\n        </Link>\n      </div>\n    </>\n  );\n};\n```\n\nIn the above code snippet, we compute the UI state and link for `previous` and `next` using the `currentPage` value.\n\n- If the current page is the first page then `previous` is `disabled`.\n- If we have the `questions` array but it is empty then `next` is `disabled`.\n\n2. Navigating Programmatically\n\n```js\nimport { useNavigate } from 'remix';\n\nexport const QuestionsRoute = () => {\n  const { questions, currentPage } = useLoaderData();\n  const navigate = useNavigate();\n\n  const handleNavigation = (e) => {\n    const { target } = e;\n    const isForwardRequest = target.getAttribute('data-nav-operation') === 'next';\n    const offset = isForwardRequest ? 1 : -1;\n\n    navigate(`/questions/all/?page=${currentPage + offset}`)\n  };\n\n  ...\n\n  return (\n    <>\n      ...\n      <div className=\"navigation\">\n        <button onClick={handleNavigation} disabled={!currentPage} data-nav-operation=\"previous\">\n          Previous\n        </button>\n        <button onClick={handleNavigation} disabled={questions && !questions.length} data-nav-operation=\"next\">\n          Next\n        </button>\n      </div>\n    </>\n  );\n};\n```\n\nIn the above code snippet, we handle the navigation programmatically i.e. rather than rendering `Links`, we render `buttons` and on clicking of the buttons, we compute the next URL.\n\n2. Reading SearchParams Client Side\n\n```js\nimport { useSearchParams } from 'remix';\n\nexport const QuestionsRoute = () => {\n  const [searchParams, setSearchParams] = useSearchParams();\n  const currentPage = parseInt(searchParams.get(\"page\") || 0, 10);\n\n  const handleClick = (e) => {\n    const { target } = e;\n    const operation = target.getAttribute(\"data-nav-operation\");\n    const offset = operation === \"next\" ? 1 : -1;\n\n    setSearchParams({ page: currentPage + offset });\n  };\n\n  ...\n\n  return (\n    <>\n      ...\n      <div className=\"navigation\">\n        <button onClick={handleNavigation} disabled={!currentPage} data-nav-operation=\"previous\">\n          Previous\n        </button>\n        <button onClick={handleNavigation} disabled={questions && !questions.length} data-nav-operation=\"next\">\n          Next\n        </button>\n      </div>\n    </>\n  );\n};\n```\n\nIn this approach, it doesn't matter if we get the `currentPage` from the server. We read the value directly from the URL and update it based on user action. Due to URL updates, the remix will re-render the components and re-run the loaders.\n\nYou can use any approach that you feel is more apt for you!\n\n## Handling Query Params\n\nThe `loader` function gets multiple parameters like `params`, `context`, and `request`. We are most interested in the `request` parameter. It is a [Fetch Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) instance with information about the current request. We are going to use the `request` parameter to access the `page` query param and modify the data fetching logic.\n\n```js\nexport const loader = async ({ request }) => {\n  // Current url: devtools.tech/questions/all?page=2\n  const url = new URL(request.url);\n  const page = url.searchParams.get(\"page\") || 1;\n  const questions = await getQuestions({\n    page,\n  });\n\n  return {\n    questions,\n    currentPage: page,\n  };\n};\n```\n\nIn the above code snippet, we create a [new URL()](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) object bypassing the current request URL. We access the current page from `searchParams` and pass that to our `getQuestions` function. Now, our Backend API reads this parameter and returns the data for that particular page.\n\n## Everything in Action\n\n<video width=\"100%\" height=\"100%\" controls>\n  <source src=\"https://ik.imagekit.io/devtoolstech/add-pagination-to-remix-run/navigation-1_ohQ5T3-36t7.mp4?ik-sdk-version=javascript-1.4.3&updatedAt=1643448454820\" type=\"video/mp4\">\n</video>\n","languages":[],"editorConfig":{}},"stats":{"views":19717,"used":0,"likes":0},"description":"","published":true,"isActive":true,"tags":["javascript","pagination","remix run","frontend","tutorial","query params","navigation","js framework"],"slug":"add-pagination-to-remix-run---rid---A9jJkhqXgojvhovKZACe","isPremium":false,"categories":[],"requires":[],"_id":"61f505741238c849f9e159e5","title":"Add Pagination to Remix Run","resourceId":"A9jJkhqXgojvhovKZACe","createdAt":1643447668464,"modifiedAt":1643449786771},"currentUser":null,"isOwner":false,"recommendations":{"questions":[{"_id":"696e107a132ce4e954ba21ec","content":{"languages":["react"],"difficulty":2},"tags":["javascript","ui","ux","devtools tech","coding","frontend ui challenge","flipkart question"],"slug":"feedback-review-system---qid---YHERU8PhP4QMDVhMjNUR","title":"Feedback Review System","questionId":"YHERU8PhP4QMDVhMjNUR"},{"_id":"65100e5bd5ab2876a4c75093","content":{"languages":["react","html"],"difficulty":1},"tags":["javascript","frontend","coding","ui","ux","reactjs","frontend coding challenge","devtools tech","devkode","javascript interview question","frontend interview question","razorpay","codedamn"],"slug":"build-a-faq-page-or-frontend-coding-challenge-or-react-js-or-html-or-css-or-javascript---qid---ayi5oGVQzM4Jkr7cIZnF","title":"Build a FAQ page | Frontend Coding Challenge | React.js | HTML | CSS | JavaScript","questionId":"ayi5oGVQzM4Jkr7cIZnF"},{"_id":"6994425a58fa65a0ece5b5f5","content":{"languages":["javascript","typescript"],"difficulty":1},"tags":["javascript","frontend","ui","ux","devtools tech","coding","programming","google","amazon","netflix","ui","ux","interview question"],"slug":"flatten-nested-object-ii---qid---VsqYPIaG0FLFrz8IUWc7","title":"Flatten Nested Object - II","questionId":"VsqYPIaG0FLFrz8IUWc7"},{"_id":"62d50c4fd3473370ac372ecb","content":{"difficulty":4,"languages":"javascript"},"tags":["javascript","promises","async programming","codedamn","frontend masters","egghead","interview","javascript quiz","javascript interview question","frontend fundamentals"],"slug":"what-is-the-output-of-the-following-code-promise-scheduling-or-event-loop-or-javascript-quiz---qid---LKBco8e783sP5ZGrjgxw","title":"What is the output of the following code? - Promise Scheduling | Event Loop | JavaScript Quiz","questionId":"LKBco8e783sP5ZGrjgxw"},{"_id":"69dc7f347aa3689d795a5f8d","content":{"languages":["javascript","typescript"],"difficulty":3},"tags":["javascript","ui","ux","devtools tech","coding","frontend","interview"],"slug":"number-to-words-converter---qid---HDauTCaVO4KLQuobnvQx","title":"Number to Words Converter","questionId":"HDauTCaVO4KLQuobnvQx"}],"resources":[{"_id":"652d6380d5ab2876a4ca20df","content":{"difficulty":4,"domain":2,"type":1,"isInternal":false,"languages":["undefined"]},"tags":["javascript","frontend","ui","ux","factset","interview experience","devtools tech","blog","medium"],"slug":"factset-software-engineer-ii-interview-experience---rid---ShYACNVRYsAX43wOki4A","title":"FactSet Software Engineer-II Interview Experience","resourceId":"ShYACNVRYsAX43wOki4A"},{"_id":"61f505741238c849f9e159e5","content":{"difficulty":4,"domain":2,"type":1,"isInternal":true},"tags":["javascript","pagination","remix run","frontend","tutorial","query params","navigation","js framework"],"slug":"add-pagination-to-remix-run---rid---A9jJkhqXgojvhovKZACe","title":"Add Pagination to Remix Run","resourceId":"A9jJkhqXgojvhovKZACe"},{"_id":"69234db2bf1a48f85e0d254f","content":{"difficulty":1,"domain":1,"type":2,"isInternal":false,"languages":[]},"tags":["javascript","ui","ux","csr","ssr","rendering","frontend system design"],"slug":"mastering-rendering-techniques-csr-ssr-streaming-with-selective-hydration---rid---Gw29cwskE3GjZbvpAcql","title":"Mastering Rendering Techniques: CSR, SSR, Streaming with Selective Hydration","resourceId":"Gw29cwskE3GjZbvpAcql"},{"_id":"6341cada42e7761bfac1a074","content":{"difficulty":2,"domain":2,"type":2,"isInternal":false},"tags":["frontend","devtools tech","tutorials","eslint","custom eslint rules","webpack","code commit","pre commit","commit hook","tooling","developer experience","remix","code masters","codechef","frontend masters","codedamn","egghead"],"slug":"how-to-improve-your-codebase-or-custom-eslint-rules-or-advanced-javascript-or-devtools-tech---rid---KgSipm8RngCFwwtI7bh2","title":"How to Improve Your Codebase!? | Custom ESLint Rules | Advanced JavaScript | Devtools Tech","resourceId":"KgSipm8RngCFwwtI7bh2"},{"_id":"62680d051195627fe9f1bf1d","content":{"difficulty":1,"domain":2,"type":2,"isInternal":false},"tags":["javascript","frontend","closures","react","devtools tech","state"," react hooks","frontend masters","egghead","codedamn","frontend fundamentals","react basis"],"slug":"what-every-react-developer-should-know-or-part-1-or-stale-closures---rid---x3PZ2ib2HWoab1RdaT2H","title":"What Every React Developer Should Know! | Part 1 | Stale Closures","resourceId":"x3PZ2ib2HWoab1RdaT2H"}]}}