From caf422e3d3d35dd8f48d7fd36aed081d779d666b Mon Sep 17 00:00:00 2001 From: timDeHof Date: Wed, 4 Oct 2023 10:20:33 -0400 Subject: [PATCH 01/10] feat(docs): add pagination tutorial and related files feat(pagination): add layout, page, and markdoc files for pagination tutorial feat(pagination): add step-1, step-2, step-3, step-4, step-5, and step-10 markdoc files The changes include adding a new page for the pagination tutorial in the docs section. Additionally, the necessary layout, page, and markdoc files are added for each step of the pagination tutorial. This allows users to learn about pagination in a React app using the Appwrite backend. The tutorial covers topics such as setting up the project, creating a database, initializing the Appwrite SDK, and seeding the database with data. feat(docs): add new files for pagination tutorial steps 6 and 7 The changes include the addition of two new files: `src/routes/docs/tutorials/pagination/step-6/+page.markdoc` and `src/routes/docs/tutorials/pagination/step-7/+page.markdoc`. These files contain the code and instructions for implementing pagination in a React application using Appwrite backend. The `step-6` file adds the initial code for displaying tasks using context and a custom hook, while the `step-7` file adds the implementation for offset pagination and the UI components for navigating through the dataset. feat(docs): add tutorial pages for implementing cursor pagination and discussing trade-offs feat(docs): add page for implementing cursor pagination in a React app using Appwrite backend A new tutorial page has been added to the documentation that explains what cursor pagination is and how to implement it in a React app using the Appwrite backend. The page provides an overview of cursor pagination and its benefits, and includes code examples for implementing cursor pagination using Appwrite's `listDocuments()` command. feat(docs): add page for discussing the trade-offs between offset pagination and cursor pagination A new tutorial page has been added to the documentation that discusses the trade-offs between offset pagination and cursor pagination. The page provides a comparison table highlighting the pros and cons of each pagination method, and offers guidance on when to use each method based on different application requirements. The page also includes a recap section summarizing the key points to consider when choosing a pagination method in Appwrite. --- src/routes/docs/tutorials/+page.svelte | 11 ++ .../docs/tutorials/pagination/+layout.svelte | 10 + .../docs/tutorials/pagination/+layout.ts | 11 ++ src/routes/docs/tutorials/pagination/+page.ts | 6 + .../tutorials/pagination/step-1/+page.markdoc | 30 +++ .../pagination/step-10/+page.markdoc | 17 ++ .../tutorials/pagination/step-2/+page.markdoc | 28 +++ .../tutorials/pagination/step-3/+page.markdoc | 57 ++++++ .../tutorials/pagination/step-4/+page.markdoc | 23 +++ .../tutorials/pagination/step-5/+page.markdoc | 63 ++++++ .../tutorials/pagination/step-6/+page.markdoc | 104 ++++++++++ .../tutorials/pagination/step-7/+page.markdoc | 181 ++++++++++++++++++ .../tutorials/pagination/step-8/+page.markdoc | 161 ++++++++++++++++ .../tutorials/pagination/step-9/+page.markdoc | 39 ++++ 14 files changed, 741 insertions(+) create mode 100644 src/routes/docs/tutorials/pagination/+layout.svelte create mode 100644 src/routes/docs/tutorials/pagination/+layout.ts create mode 100644 src/routes/docs/tutorials/pagination/+page.ts create mode 100644 src/routes/docs/tutorials/pagination/step-1/+page.markdoc create mode 100644 src/routes/docs/tutorials/pagination/step-10/+page.markdoc create mode 100644 src/routes/docs/tutorials/pagination/step-2/+page.markdoc create mode 100644 src/routes/docs/tutorials/pagination/step-3/+page.markdoc create mode 100644 src/routes/docs/tutorials/pagination/step-4/+page.markdoc create mode 100644 src/routes/docs/tutorials/pagination/step-5/+page.markdoc create mode 100644 src/routes/docs/tutorials/pagination/step-6/+page.markdoc create mode 100644 src/routes/docs/tutorials/pagination/step-7/+page.markdoc create mode 100644 src/routes/docs/tutorials/pagination/step-8/+page.markdoc create mode 100644 src/routes/docs/tutorials/pagination/step-9/+page.markdoc diff --git a/src/routes/docs/tutorials/+page.svelte b/src/routes/docs/tutorials/+page.svelte index 8306de8aff..ede777b7e4 100644 --- a/src/routes/docs/tutorials/+page.svelte +++ b/src/routes/docs/tutorials/+page.svelte @@ -50,6 +50,17 @@

+
  • + +
    +
    +

    + Learn Appwrite Pagination and more with React. +

    +
    +
  • diff --git a/src/routes/docs/tutorials/pagination/+layout.svelte b/src/routes/docs/tutorials/pagination/+layout.svelte new file mode 100644 index 0000000000..fb9fb3980f --- /dev/null +++ b/src/routes/docs/tutorials/pagination/+layout.svelte @@ -0,0 +1,10 @@ + + + diff --git a/src/routes/docs/tutorials/pagination/+layout.ts b/src/routes/docs/tutorials/pagination/+layout.ts new file mode 100644 index 0000000000..562b11506f --- /dev/null +++ b/src/routes/docs/tutorials/pagination/+layout.ts @@ -0,0 +1,11 @@ +import type { LayoutLoad } from './$types'; + +export const load: LayoutLoad = ({ url }) => { + const tutorials = import.meta.glob('./**/*.markdoc', { + eager: true + }); + return { + tutorials, + pathname: url.pathname + }; +}; diff --git a/src/routes/docs/tutorials/pagination/+page.ts b/src/routes/docs/tutorials/pagination/+page.ts new file mode 100644 index 0000000000..1726993fa1 --- /dev/null +++ b/src/routes/docs/tutorials/pagination/+page.ts @@ -0,0 +1,6 @@ +import { redirect } from '@sveltejs/kit'; +import type { PageLoad } from './$types'; + +export const load: PageLoad = async () => { + throw redirect(303, '/docs/tutorials/pagination/step-1'); +}; diff --git a/src/routes/docs/tutorials/pagination/step-1/+page.markdoc b/src/routes/docs/tutorials/pagination/step-1/+page.markdoc new file mode 100644 index 0000000000..a4b2a8db30 --- /dev/null +++ b/src/routes/docs/tutorials/pagination/step-1/+page.markdoc @@ -0,0 +1,30 @@ +--- +layout: tutorial +title: Build a todos app with React +description: Learn about using pagination in a React app using Appwrite backend. +step: 1 +difficulty: beginner +readtime: 10 +--- + +**Task App(working title)**: An app to track all your tasks that you'll need to get done. In this tutorial, you will build the Task app with Appwrite and React. + +# Overview {% #overview %} +**What is pagination in web development and why is it important?** + Pagination is a technique used in web development to divide a large dataset into smaller manageable chunks. This practice significantly enhances user experience by facilitating easy navigation through the dataset, making the data consumption less overwhelming. It's a common feature in data-rich applications, such as search engines and task management systems, offering numerous benefits like reduced load times, easier data management, and a cleaner, more organized user interface. + + In this tutorial, while building the Task App using Appwrite and React, you will implement some pagination methods that Appwrite offers to manage the display of tasks efficiently, ensuring a user-friendly interface that allows users to navigate through their tasks effortlessly. + +# Concepts {% #concepts %} + +This tutorial will introduce the following concepts: + +1. Setting up your first project with Appwrite and React. +2. Understanding databases and collections in Appwrite. +3. Crafting queries and implementing pagination to manage data efficiently. +4. Utilizing storage solution for your Todo App. + +# Prerequisites {% #prerequisites %} + +1. Basic knowledge of JavaScript and React. +2. Have the latest version of [Node.js](https://nodejs.org/en) and [NPM](https://www.npmjs.com/) installed on your computer \ No newline at end of file diff --git a/src/routes/docs/tutorials/pagination/step-10/+page.markdoc b/src/routes/docs/tutorials/pagination/step-10/+page.markdoc new file mode 100644 index 0000000000..d6268857ef --- /dev/null +++ b/src/routes/docs/tutorials/pagination/step-10/+page.markdoc @@ -0,0 +1,17 @@ +--- +layout: tutorial +title: Next Steps +description: Run your React project built with Appwrite +step: 10 +--- + +# Test your project {% #test-project %} +Run your project + + ```sh + npm run dev -- --open --port 3000 + ``` + + open [http://localhost:3000](http://localhost:3000) in your browser. + + # Further Resources {% #further-resources %} diff --git a/src/routes/docs/tutorials/pagination/step-2/+page.markdoc b/src/routes/docs/tutorials/pagination/step-2/+page.markdoc new file mode 100644 index 0000000000..40122116d7 --- /dev/null +++ b/src/routes/docs/tutorials/pagination/step-2/+page.markdoc @@ -0,0 +1,28 @@ +--- +layout: tutorial +title: Create app +description: Create a React app project and integrate with Appwrite. +step: 2 +--- + +# Create React project {% #create-react-project %} + +Create a React app with the `npm create` command. + +```sh +npm create vite@latest tasks-app -- --template react && cd tasks-app +``` + +# Add dependencies {% #add-dependencies %} + +Install the JavaScript Appwrite SDK. + +```sh +npm install appwrite +``` + +You can start the development server to watch your app update in the browser as you make changes. + +```sh +npm run dev -- --open --port 3000 +``` \ No newline at end of file diff --git a/src/routes/docs/tutorials/pagination/step-3/+page.markdoc b/src/routes/docs/tutorials/pagination/step-3/+page.markdoc new file mode 100644 index 0000000000..c34eee3fc0 --- /dev/null +++ b/src/routes/docs/tutorials/pagination/step-3/+page.markdoc @@ -0,0 +1,57 @@ +--- +layout: tutorial +title: Set up Appwrite +description: Import and initialize Appwrite for your react application. +step: 3 +--- +# Create project {% #create-project %} + +Head to the [Appwrite Console](https://cloud.appwrite.io/console). + +{% only_dark %} +![Create project screen](/images/docs/quick-starts/dark/create-project.png) +{% /only_dark %} +{% only_light %} +![Create project screen](/images/docs/quick-starts/create-project.png) +{% /only_light %} + +If this is your first time using Appwrite, create an account and create your first project. + +Then, under **Add a platform**, add a **Web app**. The **Hostname** should be localhost. + +{% only_dark %} +![Add a platform](/images/docs/quick-starts/dark/add-platform.png) +{% /only_dark %} +{% only_light %} +![Add a platform](/images/docs/quick-starts/add-platform.png) +{% /only_light %} + +You can skip optional steps. + +# Initialize Appwrite SDK {% #init-sdk %} + +To use Appwrite in our React app, we'll need to find our project ID. Find your project's ID in the **Settings** page. + +{% only_dark %} +![Project settings screen](/images/docs/quick-starts/dark/project-id.png) +{% /only_dark %} +{% only_light %} +![Project settings screen](/images/docs/quick-starts/project-id.png) +{% /only_light %} + +Create a new file `src/lib/appwrite.js` to hold our Appwrite related code. +>Only one instance of the `Client()` class should be created per app. + +Add the following code to it, replacing `` with your project ID. + +```js +import { Client, Databases } from "appwrite"; + +const client = new Client(); + +client + .setEndpoint("https://cloud.appwrite.io/v1") + .setProject("[YOUR_PROJECT_ID]"); // Replace with your project ID + +export const databases = new Databases(client); +``` \ No newline at end of file diff --git a/src/routes/docs/tutorials/pagination/step-4/+page.markdoc b/src/routes/docs/tutorials/pagination/step-4/+page.markdoc new file mode 100644 index 0000000000..43118fa535 --- /dev/null +++ b/src/routes/docs/tutorials/pagination/step-4/+page.markdoc @@ -0,0 +1,23 @@ +--- +layout: tutorial +title: Add database +description: Add a database to your React application using Appwrite Web SDK. +step: 4 +--- + +# Create collection {% #create-collection %} +In Appwrite, data is stored as a collection of documents. Create a collection in the [Appwrite Console](https://cloud.appwrite.io/) to store our ideas. + +{% only_dark %} +![Create project screen](/images/docs/tutorials/dark/idea-tracker-collection.png) +{% /only_dark %} +{% only_light %} +![Create project screen](/images/docs/tutorials/idea-tracker-collection.png) +{% /only_light %} + +Create a new collection with the following attributes: +| Field | Type | Required | +|-------------|--------|----------| +| taskId | Number | Yes | +| title | String | Yes | +| completed | boolean | Yes | diff --git a/src/routes/docs/tutorials/pagination/step-5/+page.markdoc b/src/routes/docs/tutorials/pagination/step-5/+page.markdoc new file mode 100644 index 0000000000..d38c02a889 --- /dev/null +++ b/src/routes/docs/tutorials/pagination/step-5/+page.markdoc @@ -0,0 +1,63 @@ +--- +layout: tutorial +title: Environment Setup +description: setup the environment for seeding the database +step: 5 +--- + +# Seeding the collection {% #seeding-the-collection %} +Now we need to create a script that will create documents from a setup.json file.Create a new file `./db/setup.js` in the root of the project directory and add the following code to it. + +```js +import { databases } from "../src/lib/appwrite.js"; +import { ID } from "appwrite"; + +export const TODOS_DATABASE_ID = "651c4c0bdba5256614d1"; +export const TODOS_COLLECTION_ID = "651c4c2e1e86c8de7f13"; + +async function seedCollection() { + const response = await fetch( + "https://jsonplaceholder.typicode.com/users/1/todos", + ); + const data = await response.json(); + console.log("Starting to seed collection..."); + try { + for (let i = 0; i < data.length; i++) { + const item = data[i]; + let promise = await databases.createDocument( + TODOS_DATABASE_ID, + TODOS_COLLECTION_ID, + ID.unique(), + { + taskId: item.id, + title: item.title, + completed: item.completed, + }, + ); + console.log(promise); + } + console.log("Seeding collection done!"); + } catch (error) { + console.error(error); + } +} + +seedCollection(); + +``` + +# Add Seed script to Package.json {% #add-seed-script %} + +Open the project's Package.json file and go to the `scripts` and add the following script to the list. + +```sh + "db:seed": "node ./db/setup.js", +``` + +You can now open a terminal and run the following command. + +```sh +npm run db:seed +``` + +you should see the command running in the terminal.When it's done check the collection to see it populated with the data. diff --git a/src/routes/docs/tutorials/pagination/step-6/+page.markdoc b/src/routes/docs/tutorials/pagination/step-6/+page.markdoc new file mode 100644 index 0000000000..305c6762f4 --- /dev/null +++ b/src/routes/docs/tutorials/pagination/step-6/+page.markdoc @@ -0,0 +1,104 @@ +--- +layout: tutorial +title: Adding tasks +description: Add tasks to your React application using Appwrite +step: 6 +--- +# Add Tasks context {% #add-todos-page %} + +Now we need to display our Todos to the page. In React, we can use [context](https://reactjs.org/docs/context.html) to share data between components. We'll use context and create a simple custom hook to manage out tasks. + +Create a new file `src/lib/context/tasks.jsx` and add the following code to it. + +```js +import { createContext, useContext, useEffect, useState } from "react"; +import { databases } from "../appwrite"; +import { Query } from "appwrite"; + +export const TODOS_DATABASE_ID = "[YOUR_DATABASE_ID]"; +export const TODOS_COLLECTION_ID = "[YOUR_COLLECTION_ID]"; + +const TasksContext = createContext(); + +export function useTasks() { + return useContext(TasksContext); +} + +export function TasksProvider(props) { + const [tasks, setTasks] = useState([]); + + async function init() { + try { + const response = await databases.listDocuments( + TODOS_DATABASE_ID, + TODOS_COLLECTION_ID, + [Query.orderDesc("$createdAt"), Query.limit(4)], + ); + setTasks(response.documents); + } catch (error) { + console.log(error); + } + } + + useEffect(() => { + init(); + }, []); + + return ( + + {props.children} + + ); +} +``` +we will add more functionality to the context later + +# Basic Routing + +First, wrap the `main` element with the `TasksProvider` component. + +Update `src/App.jsx` to the following code. + +```jsx +import { TasksProvider } from "./lib/context/tasks"; +import { Tasks } from "./pages/Tasks"; + +function App() { + return ( +
    + + + +
    + ); +} + +export default App; +``` + +# Tasks page + +We are able to create the Tasks page.This will show users a list of tasks that is just a small chuck of the large dataset. + +Create a new file `src/pages/Tasks.jsx` and add the following stub code to it. + +```jsx +import { useTasks } from "../lib/context/tasks"; +export function Tasks() { + const tasks = useTasks(); + return ( +
    +

    Todos

    +
      + {tasks.current.map((task) => ( +
    • + {`Task-${task.taskId}: `} + + +
    • + ))} +
    +
    + ); +} +``` \ No newline at end of file diff --git a/src/routes/docs/tutorials/pagination/step-7/+page.markdoc b/src/routes/docs/tutorials/pagination/step-7/+page.markdoc new file mode 100644 index 0000000000..bf8cee8c9a --- /dev/null +++ b/src/routes/docs/tutorials/pagination/step-7/+page.markdoc @@ -0,0 +1,181 @@ +--- +layout: tutorial +title: Implementing the Offset Pagination +description: Learn about using offset pagination in a React app using Appwrite backend. +step: 7 +--- + +# What is Offset Pagination? {% #what-is-offset-pagination %} + +Offset Pagination is widely used in databases and online APIs to extract specific subsets of data from a bigger whole.It requires you to tell it where to start ("offset") and how many records to get ("limit"). In the context of software applications, offset pagination is a technique that may be employed to facilitate the navigation of content by incorporating forward and back buttons on a given page. + +In Appwrite, offset pagination can be achieved using the `Query.limit()` and `Query.offset()` methods. Here is an example using Appwrite's `listDocuments()` command to list the next four tasks: + +```js +const page2 = await databases.listDocuments( + '[TODOS_DATABASE_ID]', + '[TODOS_COLLECTION_ID]', + [ + Query.limit(4), + Query.offset(4) + ] +); +``` +this will run and retrieve the next four tasks starting at task-5 + +# Implementation {% #implementation %} + + +# Task context + +by updating the contents of `src/lib/context/tasks.jsx` with the following: + +```jsx +import { createContext, useContext, useEffect, useState } from "react"; +import { databases } from "../appwrite"; +import { Query } from "appwrite"; + +export const TODOS_DATABASE_ID = "[YOUR_DATABASE_ID]"; +export const TODOS_COLLECTION_ID = "[YOUR_COLLECTION_ID]"; + +const TasksContext = createContext(); + +export function useTasks() { + return useContext(TasksContext); +} + +export function TasksProvider(props) { + const [tasks, setTasks] = useState([]); + const [offset, setOffset] = useState(0); + const [currentPage, setCurrentPage] = useState(1); + const [counts, setCounts] = useState({ + totalPages: 500, + totalResults: 1000, + }); + const pageLimit = 4; + const hasNext = counts.totalPages > currentPage; + + async function handleNextPage() { + if (hasNext) { + setOffset(offset + 4); + } + await init(); + } + + async function handlePreviousPage() { + if (offset > 0) { + setOffset(offset - 4); + } + await init(); + } + + async function init() { + try { + const response = await databases.listDocuments( + TODOS_DATABASE_ID, + TODOS_COLLECTION_ID, + [ + Query.orderDesc("$createdAt"), + Query.limit(pageLimit), + Query.offset(offset), + ], + ); + + setTasks(response.documents); + setCurrentPage(Math.floor(offset / pageLimit) + 1); + setCounts({ + totalPages: response.total / pageLimit, + totalResults: response.total, + }); + } catch (error) { + console.log(error); + } + } + useEffect(() => { + init(); + }, [offset]); + + return ( + + {props.children} + + ); +} +``` + +# Tasks page + +In order to utilize offset pagination in our app, we want a "forward" and "back" buttons that will allow us to move through our dataset. Also we want to have a way to show the current page too. We will add these pieces of UI to the Tasks page and use the `useTasks` hook to handle the navigation and the UI will have the following: +* a "back" button that will handle subtracting a given page limit from the offset and will be disabled when at the beginning of the dataset. +* a span tag that will show the current page. +* a "forward" button that will handle adding a given page limit to the offset and will be disabled when we reach the end of the dataset. + +Update the Tasks page in `src/pages/Tasks.jsx` to the following: + +```js +import { useEffect, useState, useCallback } from "react"; +import { useTasks } from "../lib/context/tasks"; +export function Tasks() { + const tasks = useTasks(); + const pageLimit = 4; + + return ( +
    +

    Todos

    +
      + {tasks.current && + tasks.current.map((task) => ( +
    • + {`Task-${task.taskId}: `} + + +
    • + ))} +
    + +
    + ); +} + +function Pagination(props) { + const tasks = useTasks(); + const pageLimit = 4; + + const { + totalTasks, + tasksPerPage, + nextOffset, + handleNextPage, + handlePreviousPage, + } = props; + return ( +
    + + {tasks.currentPage} + +
    + ); +} + +``` \ No newline at end of file diff --git a/src/routes/docs/tutorials/pagination/step-8/+page.markdoc b/src/routes/docs/tutorials/pagination/step-8/+page.markdoc new file mode 100644 index 0000000000..279d749deb --- /dev/null +++ b/src/routes/docs/tutorials/pagination/step-8/+page.markdoc @@ -0,0 +1,161 @@ +--- +layout: tutorial +title: Implementing the Cursor Pagination +description: Learn about using cursor pagination in a React app using Appwrite backend. +step: 8 +--- + +# What is cursor pagination? {% #what-is-cursor-pagination %} + +Cursor pagination is similar to offset pagination except it uses a cursor that is a unique identifier(often a timestamp, ID, or some other field that has a consistant order) for a specific document, which acts as a pointer to the next document we want to start querying from to get the next page of data. For example, if you have a dataset of blog posts ordered by their creation date, and you want to retrieve post after a specific data, you would use that date as the cursor. + +In Appwrite, cursor pagination can be achieved using either the `Query.cursorAfter(lastId)` query method to retrieve the next page of documents or the `Query.cursorBefore(firstId)` query method to retrieve the previous page of documents. Here is an example using Appwrite's `listDocuments()` command to retrieve the next page of documents. + +```js +const page2 = await databases.listDocuments( + '[DATABASE_ID]', + '[COLLECTION_ID]', + [ + Query.limit(25), + Query.cursorAfter(lastId), + ] +); +``` + +# Implementation {% #implementation %} + +# Task context + +by updating the contents of `src/lib/context/tasks.jsx` with the following: + +```js +import { createContext, useContext, useEffect, useState } from "react"; +import { databases } from "../appwrite"; +import { Query } from "appwrite"; + +export const TODOS_DATABASE_ID = "[YOUR_DATABASE_ID]"; +export const TODOS_COLLECTION_ID = "[YOUR_COLLECTION_ID]"; + +const TasksContext = createContext(); + +export function useTasks2() { + return useContext(TasksContext); +} + +export function TasksProvider(props) { + const [tasks, setTasks] = useState([]); + const [lastId, setLastId] = useState(null); + const [currentPage, setCurrentPage] = useState(1); + const [counts, setCounts] = useState({ + totalPages: 500, + totalResults: 1000, + }); + const pageLimit = 4; + const hasNext = counts.totalPages > currentPage; + + async function loadMore() { + console.log("loading more..."); + try { + if (hasNext) { + const response = await databases.listDocuments( + TODOS_DATABASE_ID, + TODOS_COLLECTION_ID, + [ + Query.orderDesc("$createdAt"), + Query.limit(pageLimit), + Query.cursorAfter(lastId), + ], + ); + setCurrentPage((currentPage) => + Math.min(currentPage + 1, counts.totalPages), + ); + setLastId(response.documents[response.documents.length - 1].$id); + setTasks(response.documents); + } + } catch (error) { + console.log(error); + } + } + + async function init() { + try { + const response = await databases.listDocuments( + TODOS_DATABASE_ID, + TODOS_COLLECTION_ID, + [Query.orderDesc("$createdAt"), Query.limit(pageLimit)], + ); + setLastId(response.documents[response.documents.length - 1].$id); + setTasks(response.documents); + setCounts({ + totalPages: response.total / pageLimit, + totalResults: response.total, + }); + } catch (error) { + console.log(error); + } + } + useEffect(() => { + init(); + }, []); + + return ( + + {props.children} + + ); +} + +``` + +# Tasks page + +In order to utilize cursor pagination in our app, we want a "load more" button that will allow us to load the next page of data. Also we have a way to show the current page too. We will add these pieces of the UI to the Tasks page and use the `useTasks` hook to handle the loading of the next page and have the following: +* a div that will show the current page when data is loaded +* a "load more" button that will handle loading the next page of data and will be disabled when the current page number is equal to the total number of pages. + +Update the Tasks page in `src/pages/Tasks.jsx` to the following: +```jsx +import { useEffect, useState, useCallback } from "react"; +import { useTasks } from "../lib/context/tasks"; +export function Tasks() { + const tasks = useTasks(); + const pageLimit = 4; + + return ( +
    +

    Todos

    +
      + {tasks.current && + tasks.current.map((task) => ( +
    • + {`Task-${task.taskId}: `} + + +
    • + ))} +
    + +
    + ); +} + +function Pagination() { + const tasks = useTasks(); + const pageLimit = 4; + + return ( +
    + +
    {tasks.currentPage}
    +
    + ); +} +``` \ No newline at end of file diff --git a/src/routes/docs/tutorials/pagination/step-9/+page.markdoc b/src/routes/docs/tutorials/pagination/step-9/+page.markdoc new file mode 100644 index 0000000000..aea7266261 --- /dev/null +++ b/src/routes/docs/tutorials/pagination/step-9/+page.markdoc @@ -0,0 +1,39 @@ +--- +layout: tutorial +title: Trade-offs Discussion +description: Learn about the trade-offs between each pagination method in a React app using Appwrite backend. +step: 9 +--- + +# Pros and cons {% #pros-and-cons %} + +| Feature / Aspect| Offset Pagination | Cursor Pagination | +|----------------|-----------------------|-----------------------| +| Simplicity | High | Medium to Low | +| Performance | Good for early pages, degrades with deep pagination |Consistently High | +| Data Consistency | Can be inconsistent with data changes | More consistent | +| Scalability | Good for smaller datasets | High for large datasets | +| Implementation Complexity | Low | Medium to High | +| Direct Page Access | Yes | No | + +Based on the application's requirements, you can then prioritize which features/aspects are most important and make a decision accordingly. + +# When to use each method {% #when-to-use-each-method %} + +1. Offset Pagination + * When you have a relatively small dataset that doesn't change frequently. + * When you need to jump to specific page numbers. + * When simplicity and quick Implementation are priorities. + +2. Cursor Pagination + * When dealing with large datasets + * For real-time applications or applications with frequently updating datasets. + * When deep pagination is expected (i.e., users navigating to very high page numbers). + * When performance and scalability are critical. + + # Recap {% #recap %} + * Offset pagination is straightforward and allow direct page access but can suffer from performance issues in deep pagination. + * Cursor pagination offers consistent high performance and is ideal for real-time or large datasets. + * Data Consistency challenges arise with offset pagination, especially with changing datasets. + * Cursor pagination can be more complex to implement and doesn't allow easy jumps to specific pages. + * For Appwrite, consider the dataset size, update frequency, and user navigation needs when choosing a pagination method. \ No newline at end of file From 82d6d14a56ff320d0517b82730ab8fe6663c3ad5 Mon Sep 17 00:00:00 2001 From: timDeHof Date: Thu, 5 Oct 2023 18:43:48 -0400 Subject: [PATCH 02/10] chore(docs): fix capitalization and punctuation in tutorial files The commit fixes capitalization and punctuation errors in the titles and descriptions of the tutorial files. This improves consistency and readability in the documentation. docs(pagination): update tutorial pages for offset and cursor pagination methods feat(pagination): add offset pagination method to the app The tutorial page for implementing offset pagination is updated to provide a clear explanation of what offset pagination is and how it can be used in a React app with an Appwrite backend. The code examples and instructions are also updated to reflect the changes. feat(pagination): add cursor pagination method to the app The tutorial page for implementing cursor pagination is updated to provide a clear explanation of what cursor pagination is and how it can be used in a React app with an Appwrite backend. The code examples and instructions are also updated to reflect the changes. feat(pagination): discuss trade-offs between offset and cursor pagination methods A new tutorial page is added to discuss the trade-offs between offset and cursor pagination methods. The page provides a comparison of the two methods in terms of simplicity, performance, data consistency, scalability, implementation complexity, and direct page access. It --- .../tutorials/pagination/step-1/+page.markdoc | 10 +-- .../pagination/step-10/+page.markdoc | 7 +- .../tutorials/pagination/step-2/+page.markdoc | 6 +- .../tutorials/pagination/step-3/+page.markdoc | 11 ++- .../tutorials/pagination/step-4/+page.markdoc | 4 +- .../tutorials/pagination/step-5/+page.markdoc | 25 +++--- .../tutorials/pagination/step-6/+page.markdoc | 18 +++-- .../tutorials/pagination/step-7/+page.markdoc | 27 ++++--- .../tutorials/pagination/step-8/+page.markdoc | 76 ++++++++++++------- .../tutorials/pagination/step-9/+page.markdoc | 22 +++--- 10 files changed, 117 insertions(+), 89 deletions(-) diff --git a/src/routes/docs/tutorials/pagination/step-1/+page.markdoc b/src/routes/docs/tutorials/pagination/step-1/+page.markdoc index a4b2a8db30..2aceeb1d07 100644 --- a/src/routes/docs/tutorials/pagination/step-1/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-1/+page.markdoc @@ -1,19 +1,19 @@ --- layout: tutorial -title: Build a todos app with React -description: Learn about using pagination in a React app using Appwrite backend. +title: Build a todos app with react +description: Learn about using pagination in a react app using appwrite backend. step: 1 difficulty: beginner readtime: 10 --- -**Task App(working title)**: An app to track all your tasks that you'll need to get done. In this tutorial, you will build the Task app with Appwrite and React. +**Todos App**: An app to track all your todos that you'll need to get done. In this tutorial, you will build the todos app with appwrite and react. # Overview {% #overview %} **What is pagination in web development and why is it important?** - Pagination is a technique used in web development to divide a large dataset into smaller manageable chunks. This practice significantly enhances user experience by facilitating easy navigation through the dataset, making the data consumption less overwhelming. It's a common feature in data-rich applications, such as search engines and task management systems, offering numerous benefits like reduced load times, easier data management, and a cleaner, more organized user interface. + Pagination is a technique used in web development to divide a large dataset into smaller manageable chunks. This practice enhances user experience by facilitating easy navigation through the dataset, making the data consumption less overwhelming. It's a common feature in data-rich applications, such as search engines and task management systems, offering benefits like reduced load times, easier data management, and a cleaner, more organized user interface. - In this tutorial, while building the Task App using Appwrite and React, you will implement some pagination methods that Appwrite offers to manage the display of tasks efficiently, ensuring a user-friendly interface that allows users to navigate through their tasks effortlessly. + In this tutorial, while building the Todos app using Appwrite and React, you will use some pagination methods that Appwrite offers to manage the display of tasks efficiently, ensuring a user-friendly interface that allows users to navigate through their tasks effortlessly. # Concepts {% #concepts %} diff --git a/src/routes/docs/tutorials/pagination/step-10/+page.markdoc b/src/routes/docs/tutorials/pagination/step-10/+page.markdoc index d6268857ef..4da69a7b12 100644 --- a/src/routes/docs/tutorials/pagination/step-10/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-10/+page.markdoc @@ -1,7 +1,7 @@ --- layout: tutorial -title: Next Steps -description: Run your React project built with Appwrite +title: Next steps +description: Run your react app built with appwrite step: 10 --- @@ -14,4 +14,5 @@ Run your project open [http://localhost:3000](http://localhost:3000) in your browser. - # Further Resources {% #further-resources %} + # Further resources {% #further-resources %} +Read more about it in the [pagination docs here](/docs/products/databases/pagination). \ No newline at end of file diff --git a/src/routes/docs/tutorials/pagination/step-2/+page.markdoc b/src/routes/docs/tutorials/pagination/step-2/+page.markdoc index 40122116d7..16066f8fd1 100644 --- a/src/routes/docs/tutorials/pagination/step-2/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-2/+page.markdoc @@ -1,13 +1,13 @@ --- layout: tutorial title: Create app -description: Create a React app project and integrate with Appwrite. +description: Create a react app project and integrate it with appwrite. step: 2 --- # Create React project {% #create-react-project %} -Create a React app with the `npm create` command. +Create a react app with the `npm create` command from [vite](https://vitejs.dev/). ```sh npm create vite@latest tasks-app -- --template react && cd tasks-app @@ -15,7 +15,7 @@ npm create vite@latest tasks-app -- --template react && cd tasks-app # Add dependencies {% #add-dependencies %} -Install the JavaScript Appwrite SDK. +Install the JavaScript Appwrite Web SDK. ```sh npm install appwrite diff --git a/src/routes/docs/tutorials/pagination/step-3/+page.markdoc b/src/routes/docs/tutorials/pagination/step-3/+page.markdoc index c34eee3fc0..3750297386 100644 --- a/src/routes/docs/tutorials/pagination/step-3/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-3/+page.markdoc @@ -1,7 +1,7 @@ --- layout: tutorial -title: Set up Appwrite -description: Import and initialize Appwrite for your react application. +title: Set up appwrite +description: Import and initialize appwrite for your react application. step: 3 --- # Create project {% #create-project %} @@ -30,7 +30,7 @@ You can skip optional steps. # Initialize Appwrite SDK {% #init-sdk %} -To use Appwrite in our React app, we'll need to find our project ID. Find your project's ID in the **Settings** page. +To use Appwrite in our react app, we'll need to find our project ID. Find your project's ID in the **Settings** page. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) @@ -40,7 +40,10 @@ To use Appwrite in our React app, we'll need to find our project ID. Find your p {% /only_light %} Create a new file `src/lib/appwrite.js` to hold our Appwrite related code. ->Only one instance of the `Client()` class should be created per app. + +{% info title="One Client() instance per an app" %} +Only one instance of the `Client()` class should be created per an app. +{% /info %} Add the following code to it, replacing `` with your project ID. diff --git a/src/routes/docs/tutorials/pagination/step-4/+page.markdoc b/src/routes/docs/tutorials/pagination/step-4/+page.markdoc index 43118fa535..9c34a560d9 100644 --- a/src/routes/docs/tutorials/pagination/step-4/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-4/+page.markdoc @@ -1,12 +1,12 @@ --- layout: tutorial title: Add database -description: Add a database to your React application using Appwrite Web SDK. +description: Add a database to your react application using appwrite web SDK. step: 4 --- # Create collection {% #create-collection %} -In Appwrite, data is stored as a collection of documents. Create a collection in the [Appwrite Console](https://cloud.appwrite.io/) to store our ideas. +In Appwrite, a collection of documents stores the data. You can think of it like a filing cabinet. Create a collection in the [Appwrite Console](https://cloud.appwrite.io/) to store our ideas. {% only_dark %} ![Create project screen](/images/docs/tutorials/dark/idea-tracker-collection.png) diff --git a/src/routes/docs/tutorials/pagination/step-5/+page.markdoc b/src/routes/docs/tutorials/pagination/step-5/+page.markdoc index d38c02a889..9cab73df27 100644 --- a/src/routes/docs/tutorials/pagination/step-5/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-5/+page.markdoc @@ -1,24 +1,25 @@ --- layout: tutorial -title: Environment Setup -description: setup the environment for seeding the database +title: Environment setup +description: Setup the environment for seeding the database step: 5 --- # Seeding the collection {% #seeding-the-collection %} -Now we need to create a script that will create documents from a setup.json file.Create a new file `./db/setup.js` in the root of the project directory and add the following code to it. +Now we need to create a script that will create documents from a setup.json file.Create a new file `./db/setup.js` in the root of the project directory to hold our related code. + +Add the following code to it, replacing `[YOUR_DATABASE_ID]` and `[YOUR_COLLECTION_ID]` with your project ID and collection ID. ```js import { databases } from "../src/lib/appwrite.js"; import { ID } from "appwrite"; -export const TODOS_DATABASE_ID = "651c4c0bdba5256614d1"; -export const TODOS_COLLECTION_ID = "651c4c2e1e86c8de7f13"; +export const TODOS_DATABASE_ID = "[YOUR_DATABASE_ID]"; +export const TODOS_COLLECTION_ID = "[YOUR_COLLECTION_ID]"; +const dataURL = "https://jsonplaceholder.typicode.com/users/1/todos"; async function seedCollection() { - const response = await fetch( - "https://jsonplaceholder.typicode.com/users/1/todos", - ); + const response = await fetch(dataURL); const data = await response.json(); console.log("Starting to seed collection..."); try { @@ -34,7 +35,7 @@ async function seedCollection() { completed: item.completed, }, ); - console.log(promise); + console.log(`${promise.title} has been added to the collection`); } console.log("Seeding collection done!"); } catch (error) { @@ -46,9 +47,9 @@ seedCollection(); ``` -# Add Seed script to Package.json {% #add-seed-script %} +# Add seed script to package.json {% #add-seed-script %} -Open the project's Package.json file and go to the `scripts` and add the following script to the list. +Open the project's package.json file and go to the `scripts` and add the following script to the list. ```sh "db:seed": "node ./db/setup.js", @@ -60,4 +61,4 @@ You can now open a terminal and run the following command. npm run db:seed ``` -you should see the command running in the terminal.When it's done check the collection to see it populated with the data. +You should see the command running in the terminal.When it's done check the collection to see it populated with the data. diff --git a/src/routes/docs/tutorials/pagination/step-6/+page.markdoc b/src/routes/docs/tutorials/pagination/step-6/+page.markdoc index 305c6762f4..abb73c466f 100644 --- a/src/routes/docs/tutorials/pagination/step-6/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-6/+page.markdoc @@ -1,10 +1,10 @@ --- layout: tutorial title: Adding tasks -description: Add tasks to your React application using Appwrite +description: Add tasks to your react application using appwrite step: 6 --- -# Add Tasks context {% #add-todos-page %} +# Add tasks context {% #add-tasks-context %} Now we need to display our Todos to the page. In React, we can use [context](https://reactjs.org/docs/context.html) to share data between components. We'll use context and create a simple custom hook to manage out tasks. @@ -53,13 +53,13 @@ export function TasksProvider(props) { ``` we will add more functionality to the context later -# Basic Routing +## Basic Routing {% #basic-routing %} First, wrap the `main` element with the `TasksProvider` component. Update `src/App.jsx` to the following code. -```jsx +```js import { TasksProvider } from "./lib/context/tasks"; import { Tasks } from "./pages/Tasks"; @@ -76,13 +76,13 @@ function App() { export default App; ``` -# Tasks page +### Tasks page {% #tasks-page %} -We are able to create the Tasks page.This will show users a list of tasks that is just a small chuck of the large dataset. +We are now able to create the Tasks page that will show users a list of tasks that is a small chuck of the large dataset. Create a new file `src/pages/Tasks.jsx` and add the following stub code to it. -```jsx +```js import { useTasks } from "../lib/context/tasks"; export function Tasks() { const tasks = useTasks(); @@ -101,4 +101,6 @@ export function Tasks() { ); } -``` \ No newline at end of file +``` + +In the next two steps, we will explore how to use offset or cursor pagination in our application. \ No newline at end of file diff --git a/src/routes/docs/tutorials/pagination/step-7/+page.markdoc b/src/routes/docs/tutorials/pagination/step-7/+page.markdoc index bf8cee8c9a..0ff2240b43 100644 --- a/src/routes/docs/tutorials/pagination/step-7/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-7/+page.markdoc @@ -1,13 +1,14 @@ --- layout: tutorial -title: Implementing the Offset Pagination -description: Learn about using offset pagination in a React app using Appwrite backend. +title: Implementing the offset pagination +description: Discover offset pagination. The react app will access our appwrite backend using offset pagination. step: 7 --- +In this step, we will discover how to add the offset pagination method to our app. -# What is Offset Pagination? {% #what-is-offset-pagination %} +# What is offset pagination? {% #what-is-offset-pagination %} -Offset Pagination is widely used in databases and online APIs to extract specific subsets of data from a bigger whole.It requires you to tell it where to start ("offset") and how many records to get ("limit"). In the context of software applications, offset pagination is a technique that may be employed to facilitate the navigation of content by incorporating forward and back buttons on a given page. +Offset pagination is widely used in databases and online APIs to extract specific subsets of data from a bigger whole.It requires you to tell it where to start ("offset") and how many records to get ("limit"). In the context of software applications, offset pagination is a technique that may be employed to facilitate the navigation of content by incorporating forward and back buttons on a given page. In Appwrite, offset pagination can be achieved using the `Query.limit()` and `Query.offset()` methods. Here is an example using Appwrite's `listDocuments()` command to list the next four tasks: @@ -26,11 +27,11 @@ this will run and retrieve the next four tasks starting at task-5 # Implementation {% #implementation %} -# Task context +## Task context {% #task-context %} -by updating the contents of `src/lib/context/tasks.jsx` with the following: +By updating the contents of `src/lib/context/tasks.jsx` with the following code, replacing `[YOUR_DATABASE_ID]` and `[YOUR_COLLECTION_ID]` with your project ID and collection ID. -```jsx +```js import { createContext, useContext, useEffect, useState } from "react"; import { databases } from "../appwrite"; import { Query } from "appwrite"; @@ -110,12 +111,12 @@ export function TasksProvider(props) { } ``` -# Tasks page +## Tasks page {% #tasks-page %} -In order to utilize offset pagination in our app, we want a "forward" and "back" buttons that will allow us to move through our dataset. Also we want to have a way to show the current page too. We will add these pieces of UI to the Tasks page and use the `useTasks` hook to handle the navigation and the UI will have the following: -* a "back" button that will handle subtracting a given page limit from the offset and will be disabled when at the beginning of the dataset. +For offset pagination in our app, we need "forward" and "back" buttons to navigate our dataset. We also want to display the current page. The Tasks page will contain the following UI, with navigation handled by the `useTasks` hook: +* a "back" button that will handle subtracting a given page limit from the offset and disabled when at the beginning of the dataset. * a span tag that will show the current page. -* a "forward" button that will handle adding a given page limit to the offset and will be disabled when we reach the end of the dataset. +* a "forward" button that will handle adding a given page limit to the offset and disabled when we reach the end of the dataset. Update the Tasks page in `src/pages/Tasks.jsx` to the following: @@ -178,4 +179,6 @@ function Pagination(props) { ); } -``` \ No newline at end of file +``` + +In the next step, we will replace our current pagination method with the cursor pagination method. \ No newline at end of file diff --git a/src/routes/docs/tutorials/pagination/step-8/+page.markdoc b/src/routes/docs/tutorials/pagination/step-8/+page.markdoc index 279d749deb..58d214d79a 100644 --- a/src/routes/docs/tutorials/pagination/step-8/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-8/+page.markdoc @@ -1,15 +1,16 @@ --- layout: tutorial -title: Implementing the Cursor Pagination -description: Learn about using cursor pagination in a React app using Appwrite backend. +title: Implementing the cursor pagination +description: Discover cursor pagination. The react app will access our appwrite backend using cursor pagination. step: 8 --- +In this step, we will discover how to add the cursor pagination method to our app. # What is cursor pagination? {% #what-is-cursor-pagination %} -Cursor pagination is similar to offset pagination except it uses a cursor that is a unique identifier(often a timestamp, ID, or some other field that has a consistant order) for a specific document, which acts as a pointer to the next document we want to start querying from to get the next page of data. For example, if you have a dataset of blog posts ordered by their creation date, and you want to retrieve post after a specific data, you would use that date as the cursor. +Cursor pagination uses a cursor that is a unique identifier(often a timestamp, ID, or some other field that has a consistent order) for a specific document, which acts as a pointer to the next document we want to start querying from to get the next page of data. For example, if you have a dataset of blog posts ordered by their creation date, and you want to retrieve post after a specific data, you would use that date as the cursor. -In Appwrite, cursor pagination can be achieved using either the `Query.cursorAfter(lastId)` query method to retrieve the next page of documents or the `Query.cursorBefore(firstId)` query method to retrieve the previous page of documents. Here is an example using Appwrite's `listDocuments()` command to retrieve the next page of documents. +In Appwrite, cursor pagination uses either the `Query.cursorAfter(lastId)` query method to retrieve the next page of documents or the `Query.cursorBefore(firstId)` query method to retrieve the previous page of documents. Here is an example using Appwrite's `listDocuments()` command to retrieve the next page of documents. ```js const page2 = await databases.listDocuments( @@ -24,9 +25,9 @@ const page2 = await databases.listDocuments( # Implementation {% #implementation %} -# Task context +## Task context {% #task-context %} -by updating the contents of `src/lib/context/tasks.jsx` with the following: +By updating the contents of `src/lib/context/tasks.jsx` with the following code, replacing `[YOUR_DATABASE_ID]` and `[YOUR_COLLECTION_ID]` with your project ID and collection ID. ```js import { createContext, useContext, useEffect, useState } from "react"; @@ -38,13 +39,14 @@ export const TODOS_COLLECTION_ID = "[YOUR_COLLECTION_ID]"; const TasksContext = createContext(); -export function useTasks2() { +export function useTasks() { return useContext(TasksContext); } export function TasksProvider(props) { const [tasks, setTasks] = useState([]); const [lastId, setLastId] = useState(null); + const [firstId, setFirstId] = useState(null); const [currentPage, setCurrentPage] = useState(1); const [counts, setCounts] = useState({ totalPages: 500, @@ -69,6 +71,7 @@ export function TasksProvider(props) { setCurrentPage((currentPage) => Math.min(currentPage + 1, counts.totalPages), ); + setFirstId(response.documents[0].$id); setLastId(response.documents[response.documents.length - 1].$id); setTasks(response.documents); } @@ -76,7 +79,28 @@ export function TasksProvider(props) { console.log(error); } } - + async function loadPrev() { + console.log("loading previous..."); + try { + if (hasNext) { + const response = await databases.listDocuments( + TODOS_DATABASE_ID, + TODOS_COLLECTION_ID, + [ + Query.orderDesc("$createdAt"), + Query.limit(pageLimit), + Query.cursorBefore(firstId), + ], + ); + setCurrentPage((currentPage) => Math.max(currentPage - 1, 1)); + setFirstId(response.documents[0].$id); + setLastId(response.documents[response.documents.length - 1].$id); + setTasks(response.documents); + } + } catch (error) { + console.error(error); + } + } async function init() { try { const response = await databases.listDocuments( @@ -84,6 +108,7 @@ export function TasksProvider(props) { TODOS_COLLECTION_ID, [Query.orderDesc("$createdAt"), Query.limit(pageLimit)], ); + setFirstId(response.documents[0].$id); setLastId(response.documents[response.documents.length - 1].$id); setTasks(response.documents); setCounts({ @@ -103,6 +128,7 @@ export function TasksProvider(props) { value={{ current: tasks, loadMore, + loadPrev, currentPage, hasNext, }}> @@ -110,26 +136,29 @@ export function TasksProvider(props) { ); } - ``` -# Tasks page +## Tasks page {% #tasks-page %} -In order to utilize cursor pagination in our app, we want a "load more" button that will allow us to load the next page of data. Also we have a way to show the current page too. We will add these pieces of the UI to the Tasks page and use the `useTasks` hook to handle the loading of the next page and have the following: +To utilize cursor pagination in our app, we want a "load more" button that will allow us to load the next page of data. We also have a way to show the current page too. We will add these pieces of the UI to the Tasks page and use the `useTasks` hook to handle the loading of the next page and have the following: +* a "load previous" button that will handle the loading of the previous page of data and disabled when the current page is 1. * a div that will show the current page when data is loaded -* a "load more" button that will handle loading the next page of data and will be disabled when the current page number is equal to the total number of pages. +* a "load more" button that will handle loading the next page of data and disabled when the current page number is equal to the total number of pages. Update the Tasks page in `src/pages/Tasks.jsx` to the following: -```jsx +```js import { useEffect, useState, useCallback } from "react"; import { useTasks } from "../lib/context/tasks"; export function Tasks() { - const tasks = useTasks(); - const pageLimit = 4; - - return ( + const tasks = useTasks(); + return (

    Todos

    +
      {tasks.current && tasks.current.map((task) => ( @@ -140,22 +169,11 @@ export function Tasks() { ))}
    - -
    - ); -} - -function Pagination() { - const tasks = useTasks(); - const pageLimit = 4; - - return ( -
    {tasks.currentPage}
    -
    + ); } ``` \ No newline at end of file diff --git a/src/routes/docs/tutorials/pagination/step-9/+page.markdoc b/src/routes/docs/tutorials/pagination/step-9/+page.markdoc index aea7266261..67090dfdcb 100644 --- a/src/routes/docs/tutorials/pagination/step-9/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-9/+page.markdoc @@ -1,7 +1,7 @@ --- layout: tutorial -title: Trade-offs Discussion -description: Learn about the trade-offs between each pagination method in a React app using Appwrite backend. +title: Trade-offs discussion +description: Learn about the trade-offs between each pagination method in a react app using appwrite backend. step: 9 --- @@ -10,30 +10,30 @@ step: 9 | Feature / Aspect| Offset Pagination | Cursor Pagination | |----------------|-----------------------|-----------------------| | Simplicity | High | Medium to Low | -| Performance | Good for early pages, degrades with deep pagination |Consistently High | +| Performance | Good for shallow pagination, degrades with deep pagination |Consistently High | | Data Consistency | Can be inconsistent with data changes | More consistent | | Scalability | Good for smaller datasets | High for large datasets | | Implementation Complexity | Low | Medium to High | | Direct Page Access | Yes | No | -Based on the application's requirements, you can then prioritize which features/aspects are most important and make a decision accordingly. +Based on the application's requirements, you can then determine which features/aspects are most important and make a decision accordingly. # When to use each method {% #when-to-use-each-method %} -1. Offset Pagination - * When you have a relatively small dataset that doesn't change frequently. +## Offset pagination {% #when-to-use-offset-pagination %} + * When you have a small dataset that doesn't change frequently. * When you need to jump to specific page numbers. * When simplicity and quick Implementation are priorities. -2. Cursor Pagination +## Cursor pagination {% #when-to-use-cursor-pagination %} * When dealing with large datasets * For real-time applications or applications with frequently updating datasets. - * When deep pagination is expected (i.e., users navigating to very high page numbers). + * When deep pagination is necessary (i.e., users need to navigate to high page numbers). * When performance and scalability are critical. - # Recap {% #recap %} +# Recap {% #recap %} * Offset pagination is straightforward and allow direct page access but can suffer from performance issues in deep pagination. * Cursor pagination offers consistent high performance and is ideal for real-time or large datasets. - * Data Consistency challenges arise with offset pagination, especially with changing datasets. - * Cursor pagination can be more complex to implement and doesn't allow easy jumps to specific pages. + * Offset pagination causes data consistency issues, especially with changing datasets. + * Cursor pagination requires more work and doesn't enable page skips. * For Appwrite, consider the dataset size, update frequency, and user navigation needs when choosing a pagination method. \ No newline at end of file From 648acd178c600fa94fedf9abcc368d3a789d5ab3 Mon Sep 17 00:00:00 2001 From: timDeHof Date: Thu, 19 Oct 2023 10:35:40 -0400 Subject: [PATCH 03/10] refact(pagination tutorial's steps): rewrote the steps to be more generalized and focus more on pagination. Fixed title and description to use sentence case. Changed difficulty to 'intermediate' from 'beginner'. Changed readtime to '20' from '10'. Added tabsto the different methods to showcase usage of each. --- src/routes/docs/tutorials/+page.svelte | 11 - .../tutorials/pagination/step-1/+page.markdoc | 38 +-- .../pagination/step-10/+page.markdoc | 21 +- .../tutorials/pagination/step-2/+page.markdoc | 6 +- .../tutorials/pagination/step-3/+page.markdoc | 14 +- .../tutorials/pagination/step-4/+page.markdoc | 61 +++-- .../tutorials/pagination/step-5/+page.markdoc | 18 +- .../tutorials/pagination/step-6/+page.markdoc | 70 ++--- .../tutorials/pagination/step-7/+page.markdoc | 243 ++++++++++++------ .../tutorials/pagination/step-8/+page.markdoc | 240 +++++++++++------ .../tutorials/pagination/step-9/+page.markdoc | 42 ++- 11 files changed, 485 insertions(+), 279 deletions(-) diff --git a/src/routes/docs/tutorials/+page.svelte b/src/routes/docs/tutorials/+page.svelte index ede777b7e4..8306de8aff 100644 --- a/src/routes/docs/tutorials/+page.svelte +++ b/src/routes/docs/tutorials/+page.svelte @@ -50,17 +50,6 @@

  • -
  • - -
    -
    -

    - Learn Appwrite Pagination and more with React. -

    -
    -
  • diff --git a/src/routes/docs/tutorials/pagination/step-1/+page.markdoc b/src/routes/docs/tutorials/pagination/step-1/+page.markdoc index 2aceeb1d07..4443ddcc61 100644 --- a/src/routes/docs/tutorials/pagination/step-1/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-1/+page.markdoc @@ -1,30 +1,32 @@ --- layout: tutorial -title: Build a todos app with react -description: Learn about using pagination in a react app using appwrite backend. +title: Implementing pagination with Appwrite and React +description: Dive deep into integrating pagination in a React application using the Appwrite backend step: 1 -difficulty: beginner -readtime: 10 +difficulty: intermediate +readtime: 20 --- -**Todos App**: An app to track all your todos that you'll need to get done. In this tutorial, you will build the todos app with appwrite and react. + In this tutorial, you'll learn how to seamlessly integrate Appwrite's pagination methods into a React application. Proper pagination ensures efficient data display, enhance user experience, and provide a smooth interface for navigating large datasets. -# Overview {% #overview %} -**What is pagination in web development and why is it important?** - Pagination is a technique used in web development to divide a large dataset into smaller manageable chunks. This practice enhances user experience by facilitating easy navigation through the dataset, making the data consumption less overwhelming. It's a common feature in data-rich applications, such as search engines and task management systems, offering benefits like reduced load times, easier data management, and a cleaner, more organized user interface. +# Why pagination? {% #why-pagination %} +Pagination is critical in web development, especially when working with huge databases. It helps users explore and process data without feeling overwhelmed by breaking it down into smaller parts. Data-rich applications like search engines and e-commerce sites require this. - In this tutorial, while building the Todos app using Appwrite and React, you will use some pagination methods that Appwrite offers to manage the display of tasks efficiently, ensuring a user-friendly interface that allows users to navigate through their tasks effortlessly. +The benefits are numerous: +* faster load times +* streamlined data management +* a tidier, more user-friendly interface -# Concepts {% #concepts %} - -This tutorial will introduce the following concepts: - -1. Setting up your first project with Appwrite and React. -2. Understanding databases and collections in Appwrite. -3. Crafting queries and implementing pagination to manage data efficiently. -4. Utilizing storage solution for your Todo App. +# Learning Objectives {% #learning-objectives %} +By the end of this tutorial, you'll be equipped with: +1. The knowledge to set up a React project integrated with Appwrite. +2. Skills to craft queries using offset pagination and understand its implications. +3. Expertise in constructing queries with cursor pagination and recognizing its benefits. +4. Insight into the pros and cons of different pagination techniques. # Prerequisites {% #prerequisites %} +Before diving in, ensure you: 1. Basic knowledge of JavaScript and React. -2. Have the latest version of [Node.js](https://nodejs.org/en) and [NPM](https://www.npmjs.com/) installed on your computer \ No newline at end of file +2. Basic understanding of databases or backend systems. +3. Have the latest version of [Node.js](https://nodejs.org/en) and [npm](https://www.npmjs.com/) installed on your computer. \ No newline at end of file diff --git a/src/routes/docs/tutorials/pagination/step-10/+page.markdoc b/src/routes/docs/tutorials/pagination/step-10/+page.markdoc index 4da69a7b12..5b7137a432 100644 --- a/src/routes/docs/tutorials/pagination/step-10/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-10/+page.markdoc @@ -1,18 +1,23 @@ --- layout: tutorial -title: Next steps -description: Run your react app built with appwrite +title: Finalizing and further exploration +description: Test your pagination Implementation in React with Appwrite and discover more resources to deepen your understanding. step: 10 --- - -# Test your project {% #test-project %} -Run your project +# Wrapping up {% #wrapping-up %} +Pagination is a fundamental feature for applications that handle large datasets. By now, you should have a solid understanding of how to implement different pagination methods using Appwrite with React. +# Test your pagination implementation {% #test-your-pagination-implementation %} +Before deploying or integrating your pagination into a larger project, it's crucial to test it thoroughly: ```sh npm run dev -- --open --port 3000 ``` - open [http://localhost:3000](http://localhost:3000) in your browser. + Navigate to [http://localhost:3000](http://localhost:3000) in your browser to interact with your application. Ensure that both offset and cursor pagination methods work as expected, and data loads correctly as you navigate through pages. + + # Dive deeper {% #dive-deeper %} + + To deepen your understanding and explore more advanced pagination techniques or nuances, refer to the official documentation: +[Appwrite pagination documentation](/docs/products/databases/pagination). - # Further resources {% #further-resources %} -Read more about it in the [pagination docs here](/docs/products/databases/pagination). \ No newline at end of file +Remember, the choice between offset and cursor pagination will depend on your application's specific needs. Always consider the size of the dataset, usr experience, and performance requirements when making your decision. \ No newline at end of file diff --git a/src/routes/docs/tutorials/pagination/step-2/+page.markdoc b/src/routes/docs/tutorials/pagination/step-2/+page.markdoc index 16066f8fd1..ceba13e534 100644 --- a/src/routes/docs/tutorials/pagination/step-2/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-2/+page.markdoc @@ -1,16 +1,16 @@ --- layout: tutorial title: Create app -description: Create a react app project and integrate it with appwrite. +description: Create a React app project and integrate it with Appwrite. step: 2 --- # Create React project {% #create-react-project %} -Create a react app with the `npm create` command from [vite](https://vitejs.dev/). +Create a React app with the `npm create` command from [Vite](https://vitejs.dev/). ```sh -npm create vite@latest tasks-app -- --template react && cd tasks-app +npm create vite@latest todo-app -- --template react && cd todo-app ``` # Add dependencies {% #add-dependencies %} diff --git a/src/routes/docs/tutorials/pagination/step-3/+page.markdoc b/src/routes/docs/tutorials/pagination/step-3/+page.markdoc index 3750297386..ec10bfb47e 100644 --- a/src/routes/docs/tutorials/pagination/step-3/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-3/+page.markdoc @@ -1,7 +1,7 @@ --- layout: tutorial -title: Set up appwrite -description: Import and initialize appwrite for your react application. +title: Set up Appwrite +description: Import and initialize Appwrite for your React application. step: 3 --- # Create project {% #create-project %} @@ -17,7 +17,7 @@ Head to the [Appwrite Console](https://cloud.appwrite.io/console). If this is your first time using Appwrite, create an account and create your first project. -Then, under **Add a platform**, add a **Web app**. The **Hostname** should be localhost. +Next, under **Add a platform**, choose **Web app**. For the **Hostname**, use "localhost". {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) @@ -30,7 +30,7 @@ You can skip optional steps. # Initialize Appwrite SDK {% #init-sdk %} -To use Appwrite in our react app, we'll need to find our project ID. Find your project's ID in the **Settings** page. +To use Appwrite in our React app, we need to identify our project ID. You can find this ID on the **Settings** page. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) @@ -39,13 +39,13 @@ To use Appwrite in our react app, we'll need to find our project ID. Find your p ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} -Create a new file `src/lib/appwrite.js` to hold our Appwrite related code. +Create a new file `src/lib/appwrite.js` to store our Appwrite-related code. {% info title="One Client() instance per an app" %} -Only one instance of the `Client()` class should be created per an app. +It's recommended to create only one instance of the `Client()` class for each app. {% /info %} -Add the following code to it, replacing `` with your project ID. +Insert the following code, making sure to replace `` with your actual project ID. ```js import { Client, Databases } from "appwrite"; diff --git a/src/routes/docs/tutorials/pagination/step-4/+page.markdoc b/src/routes/docs/tutorials/pagination/step-4/+page.markdoc index 9c34a560d9..c9822ce1b8 100644 --- a/src/routes/docs/tutorials/pagination/step-4/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-4/+page.markdoc @@ -1,23 +1,54 @@ --- layout: tutorial title: Add database -description: Add a database to your react application using appwrite web SDK. +description: Add a database to your React application using Appwrite Web SDK. step: 4 --- +Databases are essential for dynamic applications, ensuring data is organized and accessible. They form the backbone for data storage and management. In this step, we'll integrate a database into our application using the Appwrite Web SDK, setting the stage for more advanced functionalities. -# Create collection {% #create-collection %} -In Appwrite, a collection of documents stores the data. You can think of it like a filing cabinet. Create a collection in the [Appwrite Console](https://cloud.appwrite.io/) to store our ideas. +# Create a database {% #create-a-database %} +In Appwrite's console, navigate to the **Databases** page and click **Create database** and name it "development". -{% only_dark %} -![Create project screen](/images/docs/tutorials/dark/idea-tracker-collection.png) -{% /only_dark %} -{% only_light %} -![Create project screen](/images/docs/tutorials/idea-tracker-collection.png) -{% /only_light %} +# Create a collection {% #create-collection %} +To create a collection, head to the **Databases** page, find to your database, and click **Create collection**. Name the collection "Todos" as it will store our todo items. -Create a new collection with the following attributes: -| Field | Type | Required | -|-------------|--------|----------| -| taskId | Number | Yes | -| title | String | Yes | -| completed | boolean | Yes | +When setting up your collection, make sure it has these attributes: + +{% table %} +*   {% width=10 %} +* Field {% width=10 %} +* Type {% width=10 %} +* Required {% width=10 %} +* Size {% width=10 %} +* Min {% width=10 %} +* Max {% width=10 %} +--- +*   +* todoId +* Integer +* Yes +* - +* 0 +* 500 +--- +*   +* title +* String +* Yes +* 248 +* - +* - +--- +*   +* completed +* Boolean +* Yes +* 2048 +* - +* - +{% /table %} + +# Add Permissions {% #add-permissions %} +1. Go to the 'Collection Settings' tab. +2. Scroll to the 'Permissions' section. +3. Add the `Any` role, ensuring you select both `CREATE` and `READ` permissions. diff --git a/src/routes/docs/tutorials/pagination/step-5/+page.markdoc b/src/routes/docs/tutorials/pagination/step-5/+page.markdoc index 9cab73df27..044d93509b 100644 --- a/src/routes/docs/tutorials/pagination/step-5/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-5/+page.markdoc @@ -6,9 +6,9 @@ step: 5 --- # Seeding the collection {% #seeding-the-collection %} -Now we need to create a script that will create documents from a setup.json file.Create a new file `./db/setup.js` in the root of the project directory to hold our related code. +We'll now create a script that populates documents from a `setup.json` file.Create a new file `./db/setup.js` in the root of your project directory for this purpose. -Add the following code to it, replacing `[YOUR_DATABASE_ID]` and `[YOUR_COLLECTION_ID]` with your project ID and collection ID. +Insert the following code, make sure to replace `[YOUR_DATABASE_ID]` and `[YOUR_COLLECTION_ID]` with your respective project and collection IDs. ```js import { databases } from "../src/lib/appwrite.js"; @@ -49,16 +49,22 @@ seedCollection(); # Add seed script to package.json {% #add-seed-script %} -Open the project's package.json file and go to the `scripts` and add the following script to the list. +Open the project's package.json file, navigate to the `scripts` section, and add the following script: ```sh - "db:seed": "node ./db/setup.js", +{ + ..., + "scripts": { + ... + "db:seed": "node ./db/setup.js" + }, +} ``` -You can now open a terminal and run the following command. +Afterwards, open a terminal and execute: ```sh npm run db:seed ``` -You should see the command running in the terminal.When it's done check the collection to see it populated with the data. +You should see the process running in the terminal.Once it's completed, check your collection to verify that it's populated with the data. diff --git a/src/routes/docs/tutorials/pagination/step-6/+page.markdoc b/src/routes/docs/tutorials/pagination/step-6/+page.markdoc index abb73c466f..22507bc167 100644 --- a/src/routes/docs/tutorials/pagination/step-6/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-6/+page.markdoc @@ -1,31 +1,31 @@ --- layout: tutorial -title: Adding tasks -description: Add tasks to your react application using appwrite +title: Adding todos +description: Add todos to your React application using Appwrite. step: 6 --- -# Add tasks context {% #add-tasks-context %} +# Add todos context {% #add-todos-context %} -Now we need to display our Todos to the page. In React, we can use [context](https://reactjs.org/docs/context.html) to share data between components. We'll use context and create a simple custom hook to manage out tasks. +We'll now display our Todos on the page. In React, [context](https://reactjs.org/docs/context.html) allows us to share data across components. We'll leverage context and create a custom hook to manage our todos. -Create a new file `src/lib/context/tasks.jsx` and add the following code to it. +Create a new file named `src/lib/context/todos.jsx` and insert the following code: ```js import { createContext, useContext, useEffect, useState } from "react"; import { databases } from "../appwrite"; import { Query } from "appwrite"; -export const TODOS_DATABASE_ID = "[YOUR_DATABASE_ID]"; -export const TODOS_COLLECTION_ID = "[YOUR_COLLECTION_ID]"; +const TODOS_DATABASE_ID = "[YOUR_DATABASE_ID]"; +const TODOS_COLLECTION_ID = "[YOUR_COLLECTION_ID]"; -const TasksContext = createContext(); +const TodosContext = createContext(); -export function useTasks() { - return useContext(TasksContext); +export function useTodos() { + return useContext(TodosContext); } -export function TasksProvider(props) { - const [tasks, setTasks] = useState([]); +export function TodosProvider(props) { + const [todos, setTodos] = useState([]); async function init() { try { @@ -34,7 +34,7 @@ export function TasksProvider(props) { TODOS_COLLECTION_ID, [Query.orderDesc("$createdAt"), Query.limit(4)], ); - setTasks(response.documents); + setTodos(response.documents); } catch (error) { console.log(error); } @@ -45,30 +45,30 @@ export function TasksProvider(props) { }, []); return ( - + {props.children} - + ); } ``` -we will add more functionality to the context later +We'll expand on this context's functionality later. ## Basic Routing {% #basic-routing %} -First, wrap the `main` element with the `TasksProvider` component. +First, enclose the `main` element with the `TodosProvider` component. -Update `src/App.jsx` to the following code. +Update `src/App.jsx` with the following code: ```js -import { TasksProvider } from "./lib/context/tasks"; -import { Tasks } from "./pages/Tasks"; +import { TodosProvider } from "./lib/context/todos"; +import { Todos } from "./pages/Todos"; function App() { return (
    - - - + + +
    ); } @@ -76,25 +76,25 @@ function App() { export default App; ``` -### Tasks page {% #tasks-page %} +### Todos page {% #todos-page %} -We are now able to create the Tasks page that will show users a list of tasks that is a small chuck of the large dataset. +Now, we can craft the Todos page, which will present users with a list of todos-a small chuck of the large dataset. -Create a new file `src/pages/Tasks.jsx` and add the following stub code to it. +Create a new file named `src/pages/Todos.jsx` and insert this placeholder code: ```js -import { useTasks } from "../lib/context/tasks"; -export function Tasks() { - const tasks = useTasks(); +import { useTodos } from "../lib/context/todos"; +export function Todos() { + const todos = useTodos(); return (

    Todos

      - {tasks.current.map((task) => ( -
    • - {`Task-${task.taskId}: `} - - + {todos.current.map((todo) => ( +
    • + {`Task-${todo.taskId}: `} + +
    • ))}
    @@ -103,4 +103,4 @@ export function Tasks() { } ``` -In the next two steps, we will explore how to use offset or cursor pagination in our application. \ No newline at end of file +The next steps will involve integrating offset or cursor pagination in our application. diff --git a/src/routes/docs/tutorials/pagination/step-7/+page.markdoc b/src/routes/docs/tutorials/pagination/step-7/+page.markdoc index 0ff2240b43..de897b8d34 100644 --- a/src/routes/docs/tutorials/pagination/step-7/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-7/+page.markdoc @@ -1,16 +1,15 @@ --- layout: tutorial title: Implementing the offset pagination -description: Discover offset pagination. The react app will access our appwrite backend using offset pagination. +description: Explore offset pagination and how the react app accesses the Appwrite backend using this method. step: 7 --- -In this step, we will discover how to add the offset pagination method to our app. +In this step, we'll delve into adding the offset pagination method to our app. # What is offset pagination? {% #what-is-offset-pagination %} +Offset pagination is widely used in databases and online APIs to extract specific data subsets from a bigger whole. It requires you to tell it where to start(offset) and how many records to get(limit). In the context of software applications, offset pagination is a technique that may be employed to facilitate the content navigation by incorporating forward and back buttons on a given page. -Offset pagination is widely used in databases and online APIs to extract specific subsets of data from a bigger whole.It requires you to tell it where to start ("offset") and how many records to get ("limit"). In the context of software applications, offset pagination is a technique that may be employed to facilitate the navigation of content by incorporating forward and back buttons on a given page. - -In Appwrite, offset pagination can be achieved using the `Query.limit()` and `Query.offset()` methods. Here is an example using Appwrite's `listDocuments()` command to list the next four tasks: +With Appwrite, offset pagination is achieved using the `Query.limit()` and `Query.offset()` methods. Here's an example using Appwrite's `listDocuments()` function to list the next four tasks: ```js const page2 = await databases.listDocuments( @@ -22,31 +21,96 @@ const page2 = await databases.listDocuments( ] ); ``` -this will run and retrieve the next four tasks starting at task-5 +this will retrieve the next four tasks, starting at task-5 + +We'll explore different UI patterns for offset pagination and how to implement them in our React app. + +# Creating the Pagination Component {% #creating-the-pagination-component %} +{% tabs %} +{% tabsitem #Basic-pagination title="Basic Pagination" %} + +Create a file and named `src/components/BasicPagination.jsx`, insert the following code: +```js +import React from 'react'; + +function BasicPagination(props) { + const { loadPrev, loadNext, hasNext, hasPrev } = props; + + return ( +
    + + +
    + ); +} + +export default BasicPagination; + +``` +This component will only have "Previous" and "Next" buttons. +{% /tabsitem %} +{% tabsitem #numbered-pagination title="Numbered Pagination" %} +Create a file and named `src/components/NumberedPagination.jsx`, insert the following code: +```js +import React from 'react'; + +function NumberedPagination(props) { + const { + currentPage, + totalPages, + loadPrev, + loadNext, + loadPage, + hasNext, + hasPrev + } = props; + + const pages = []; + + for (let i = 1; i <= totalPages; i++) { + pages.push( + , + ); + } + + return ( +
    + + {pages + +
    + ); +} -# Implementation {% #implementation %} +export default NumberedPagination; +``` +This component will have "Previous" and "Next" buttons along with numbered pages for direct navigation. +{% /tabsitem %} +{% /tabs %} -## Task context {% #task-context %} +# Update the todos context {% #update-the-todos-context %} -By updating the contents of `src/lib/context/tasks.jsx` with the following code, replacing `[YOUR_DATABASE_ID]` and `[YOUR_COLLECTION_ID]` with your project ID and collection ID. +Update the contents of `src/lib/context/todos.jsx` with the following code, replacing `[YOUR_DATABASE_ID]` and `[YOUR_COLLECTION_ID]` with your project ID and collection ID. ```js import { createContext, useContext, useEffect, useState } from "react"; import { databases } from "../appwrite"; import { Query } from "appwrite"; -export const TODOS_DATABASE_ID = "[YOUR_DATABASE_ID]"; -export const TODOS_COLLECTION_ID = "[YOUR_COLLECTION_ID]"; +const TODOS_DATABASE_ID = "[YOUR_DATABASE_ID]"; +const TODOS_COLLECTION_ID = "[YOUR_COLLECTION_ID]"; -const TasksContext = createContext(); +const TodosContext = createContext(); -export function useTasks() { - return useContext(TasksContext); +export function useTodos() { + return useContext(TodosContext); } -export function TasksProvider(props) { - const [tasks, setTasks] = useState([]); +export function TodosProvider(props) { + const [todos, setTodos] = useState([]); const [offset, setOffset] = useState(0); const [currentPage, setCurrentPage] = useState(1); const [counts, setCounts] = useState({ @@ -56,18 +120,20 @@ export function TasksProvider(props) { const pageLimit = 4; const hasNext = counts.totalPages > currentPage; - async function handleNextPage() { + async function loadNext() { if (hasNext) { - setOffset(offset + 4); + setOffset(offset + pageLimit); } - await init(); } - async function handlePreviousPage() { - if (offset > 0) { - setOffset(offset - 4); + async function loadPrev() { + if (offset >= pageLimit) { + setOffset(offset - pageLimit); } - await init(); + } + + async function loadPage(pageNumber) { + setOffset((pageNumber - 1) * pageLimit); } async function init() { @@ -76,13 +142,13 @@ export function TasksProvider(props) { TODOS_DATABASE_ID, TODOS_COLLECTION_ID, [ - Query.orderDesc("$createdAt"), + Query.orderAsc("$createdAt"), Query.limit(pageLimit), Query.offset(offset), ], ); - setTasks(response.documents); + setTodos(response.documents); setCurrentPage(Math.floor(offset / pageLimit) + 1); setCounts({ totalPages: response.total / pageLimit, @@ -97,88 +163,103 @@ export function TasksProvider(props) { }, [offset]); return ( - 1, }}> {props.children} - + ); } ``` +In this updated context: + * We're using `Query.limit()` to set the number of tasks we want to fetch per page. + * We're using `Query.offset()` to set the starting point for fetching tasks based on the current page. + * We've added `loadNextPage`, `loadPrevPage`, and `loadPage` function to handle pagination. -## Tasks page {% #tasks-page %} - -For offset pagination in our app, we need "forward" and "back" buttons to navigate our dataset. We also want to display the current page. The Tasks page will contain the following UI, with navigation handled by the `useTasks` hook: -* a "back" button that will handle subtracting a given page limit from the offset and disabled when at the beginning of the dataset. -* a span tag that will show the current page. -* a "forward" button that will handle adding a given page limit to the offset and disabled when we reach the end of the dataset. + Now, you can use this context in your components to display todo items and handle pagination using Appwrite's offset pagination methods. -Update the Tasks page in `src/pages/Tasks.jsx` to the following: +# Update the Todos page {% #update-the-todoss-page %} +Now, let's use our Pagination component in the Todos page. +{% tabs %} +{% tabsitem #Basic-pagination title="Basic Pagination" %} +Update the Todos page in `src/pages/Todos.jsx`: ```js -import { useEffect, useState, useCallback } from "react"; -import { useTasks } from "../lib/context/tasks"; -export function Tasks() { - const tasks = useTasks(); - const pageLimit = 4; +import BasicPagination from '../components/BasicPagination'; +import {useTodos} from "../lib/context/todos"; +export function TodosWithBasicPagination() { + const todos = useTodos(); return (

    Todos

      - {tasks.current && - tasks.current.map((task) => ( -
    • - {`Task-${task.taskId}: `} - - -
    • - ))} + {todos.current.map(todo => ( +
    • + {`Task-${todo.taskId}: `} + + +
    • + ))}
    - 1} />
    ); } +``` +{% /tabsitem %} +{% tabsitem #numbered-pagination title="Numbered Pagination" %} +Update the Todos page in `src/pages/Todos.jsx`: +```js +import NumberedPagination from '../components/NumberedPagination'; -function Pagination(props) { +export function TasksWithNumberedPagination() { const tasks = useTasks(); - const pageLimit = 4; - const { - totalTasks, - tasksPerPage, - nextOffset, - handleNextPage, - handlePreviousPage, - } = props; return ( -
    - - {tasks.currentPage} - -
    +
    +

    Todos

    +
      + {tasks.current.map(task => ( +
    • + {`Task-${task.taskId}: `} + + +
    • + ))} +
    + 1} + /> +
    ); } - ``` +{% /tabsitem %} +{% /tabs %} + +You can now choose between the two offset pagination styles based on your UI/UX preferences. + +In the next step, we'll transition from our current pagination method to cursor pagination method. -In the next step, we will replace our current pagination method with the cursor pagination method. \ No newline at end of file +# See it in action {% #see-it-in-action %} +Before moving on, take a moment to run your application and see the offset pagination in action. Navigate through the tasks using the "forward" and "back" buttons and observe how the tasks are displayed based on the offset. This hands-on experience will give you a clearer understanding of how offset pagination works in a real-world scenario. \ No newline at end of file diff --git a/src/routes/docs/tutorials/pagination/step-8/+page.markdoc b/src/routes/docs/tutorials/pagination/step-8/+page.markdoc index 58d214d79a..fea4ad9cb6 100644 --- a/src/routes/docs/tutorials/pagination/step-8/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-8/+page.markdoc @@ -1,16 +1,15 @@ --- layout: tutorial title: Implementing the cursor pagination -description: Discover cursor pagination. The react app will access our appwrite backend using cursor pagination. +description: Explore cursor pagination and how the React app accesses the appwrite backend using this method. step: 8 --- -In this step, we will discover how to add the cursor pagination method to our app. +In this step, we'll delve into adding the cursor pagination method to our app. # What is cursor pagination? {% #what-is-cursor-pagination %} +Cursor pagination uses a unique identifier(often a timestamp, ID, or another consistently ordered field) for a specific document. This identifer acts as a pointer to the next document we want to start querying from to get the next page of data. For instance, if you have a dataset of blog posts ordered by their creation date and you want to retrieve post after a specific data, you would use that date as the cursor. -Cursor pagination uses a cursor that is a unique identifier(often a timestamp, ID, or some other field that has a consistent order) for a specific document, which acts as a pointer to the next document we want to start querying from to get the next page of data. For example, if you have a dataset of blog posts ordered by their creation date, and you want to retrieve post after a specific data, you would use that date as the cursor. - -In Appwrite, cursor pagination uses either the `Query.cursorAfter(lastId)` query method to retrieve the next page of documents or the `Query.cursorBefore(firstId)` query method to retrieve the previous page of documents. Here is an example using Appwrite's `listDocuments()` command to retrieve the next page of documents. +With Appwrite, cursor pagination is achieved using the `Query.cursorAfter(lastId)` or the `Query.cursorBefore(firstId)` query methods. Here is an example using Appwrite's `listDocuments()` function to retrieve the next page of documents: ```js const page2 = await databases.listDocuments( @@ -22,43 +21,110 @@ const page2 = await databases.listDocuments( ] ); ``` +Cursor pagination is typically used in scenarios where the dataset is large, and the exact number of total items might not be known upfront. Here are different ways you can utilize cursor pagination: + +# Creating the Pagination Component {% #creating-the-pagination-component %} +{% tabs %} +{% tabsitem #Basic-cursor-pagination title="Basic Cursor Pagination" %} + +Create a file and named `src/components/BasicCursorPagination.jsx`, insert the following code: +```js + +function CursorPagination(props) { + const { + hasNext, + loadMore, + loadPrev, + currentPage + } = props; + + return ( +
    + + Page {currentPage} + +
    + ); +} + +export default CursorPagination; + +``` +This is the most straightforward use of cursor pagination. You have "Load Previous" and "Load More" buttons, and you fetch data based on the last or first items's cursor. +{% /tabsitem %} +{% tabsitem #infinite-scroll-with-cursor title="Infinite Scroll with Cursor" %} +Create a file and named `src/components/InfiniteScroll.jsx`, insert the following code: +```js +import React, { useEffect } from 'react'; + +function InfiniteScroll(props) { + const { + hasNext, + loadMore, + current + } = props; + + useEffect(() => { + const handleScroll = () => { + // Check if we're at the bottom of the viewport + if (window.innerHeight + document.documentElement.scrollTop !== document.documentElement.offsetHeight) return; + if (hasNext && current && current.length) { + loadMore(); + } + }; -# Implementation {% #implementation %} + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, [hasNext, current]); + + return ( +
    + {props.children} + {hasNext &&
    Loading more tasks...
    } +
    + ); +} + +export default InfiniteScroll; +``` +Instead of manually clicking a "load more" button, you can automatically fetch more items when the user scrolls to the bottom of the list. This provides a seamless experience for users. +{% /tabsitem %} +{% /tabs %} -## Task context {% #task-context %} +# Update todos context {% #updating-todos-context %} -By updating the contents of `src/lib/context/tasks.jsx` with the following code, replacing `[YOUR_DATABASE_ID]` and `[YOUR_COLLECTION_ID]` with your project ID and collection ID. +Update the contents of `src/lib/context/todos.jsx` with the following code, replacing `[YOUR_DATABASE_ID]` and `[YOUR_COLLECTION_ID]` with your project ID and collection ID. ```js import { createContext, useContext, useEffect, useState } from "react"; import { databases } from "../appwrite"; import { Query } from "appwrite"; -export const TODOS_DATABASE_ID = "[YOUR_DATABASE_ID]"; -export const TODOS_COLLECTION_ID = "[YOUR_COLLECTION_ID]"; +const TODOS_DATABASE_ID = "[YOUR_DATABASE_ID]"; +const TODOS_COLLECTION_ID = "[YOUR_COLLECTION_ID]"; -const TasksContext = createContext(); +const TodosContext = createContext(); -export function useTasks() { - return useContext(TasksContext); +export function useTodos() { + return useContext(TodosContext); } -export function TasksProvider(props) { - const [tasks, setTasks] = useState([]); +export function TodosProvider(props) { + const [todos, setTodos] = useState([]); const [lastId, setLastId] = useState(null); const [firstId, setFirstId] = useState(null); const [currentPage, setCurrentPage] = useState(1); - const [counts, setCounts] = useState({ - totalPages: 500, - totalResults: 1000, - }); + const [totalPages, setTotalPages] = useState(0); const pageLimit = 4; - const hasNext = counts.totalPages > currentPage; + const hasNext = totalPages > currentPage; async function loadMore() { - console.log("loading more..."); + if (currentPage < totalPages) { try { - if (hasNext) { const response = await databases.listDocuments( TODOS_DATABASE_ID, TODOS_COLLECTION_ID, @@ -68,55 +134,49 @@ export function TasksProvider(props) { Query.cursorAfter(lastId), ], ); - setCurrentPage((currentPage) => - Math.min(currentPage + 1, counts.totalPages), - ); + setCurrentPage(currentPage + 1) setFirstId(response.documents[0].$id); setLastId(response.documents[response.documents.length - 1].$id); - setTasks(response.documents); - } + setTodos([...todos, ...response.documents]); } catch (error) { - console.log(error); + console.log("Error loading more todos:", error); + } } } async function loadPrev() { - console.log("loading previous..."); + if (currentPage > 1) { try { - if (hasNext) { const response = await databases.listDocuments( TODOS_DATABASE_ID, TODOS_COLLECTION_ID, [ - Query.orderDesc("$createdAt"), + Query.orderAsc("$createdAt"), Query.limit(pageLimit), Query.cursorBefore(firstId), ], ); - setCurrentPage((currentPage) => Math.max(currentPage - 1, 1)); + setCurrentPage(currentPage -1 ); setFirstId(response.documents[0].$id); setLastId(response.documents[response.documents.length - 1].$id); setTasks(response.documents); - } } catch (error) { - console.error(error); + console.error("Error loading previous todos:", error); } } + } async function init() { try { const response = await databases.listDocuments( TODOS_DATABASE_ID, TODOS_COLLECTION_ID, - [Query.orderDesc("$createdAt"), Query.limit(pageLimit)], + [Query.orderAsc("$createdAt"), Query.limit(pageLimit)], ); setFirstId(response.documents[0].$id); setLastId(response.documents[response.documents.length - 1].$id); setTasks(response.documents); - setCounts({ - totalPages: response.total / pageLimit, - totalResults: response.total, - }); + setTotalPages(Math.ceil(response.total / pageLimit)); } catch (error) { - console.log(error); + console.log("Error initializing tasks:", error); } } useEffect(() => { @@ -124,56 +184,92 @@ export function TasksProvider(props) { }, []); return ( - {props.children} - + ); } ``` +In this updated context: + * We're using `Query.cursorAfter()` is used to fetch documents that come after a specific document. + * We're using `Query.cursorBefore()` is used to fetch documents that come before a specific document. + * We've added `lastId` to hold the ID of the last document in the current set of todos.By using this ID with `Query.cursorAfter()`, We're essentially telling Appwrite: "Give me the next set of todos that come after this specific todo item." + * We's added `firstId` to hold the ID of the first document in the current set of todos. By using this ID with `Query.cursorBefore()`, we're telling Appwrite: "Give me the set of todos that come before this specific todo item." -## Tasks page {% #tasks-page %} +Now, you can use this context in your components to display todo items and handle pagination using Appwrite's cursor pagination methods. +# Update Todos page {% #update-todos-page %} +Now, let's use our Pagination component in the Todos Page. +{% tabs %} +{% tabsitem #Basic-cursor-pagination title="Basic Cursor Pagination" %} +Update the Todos page in `src/pages/Todos.jsx`: +```js +import React from 'react'; +import { useTodos } from '../lib/context/todos'; +import BasicCursorPagination from '../components/BasicCursorPagination'; -To utilize cursor pagination in our app, we want a "load more" button that will allow us to load the next page of data. We also have a way to show the current page too. We will add these pieces of the UI to the Tasks page and use the `useTasks` hook to handle the loading of the next page and have the following: -* a "load previous" button that will handle the loading of the previous page of data and disabled when the current page is 1. -* a div that will show the current page when data is loaded -* a "load more" button that will handle loading the next page of data and disabled when the current page number is equal to the total number of pages. +export function TodosPage() { + const todos = useTodos(); -Update the Tasks page in `src/pages/Tasks.jsx` to the following: -```js -import { useEffect, useState, useCallback } from "react"; -import { useTasks } from "../lib/context/tasks"; -export function Tasks() { - const tasks = useTasks(); - return ( + return (

    Todos

    -
      - {tasks.current && - tasks.current.map((task) => ( -
    • - {`Task-${task.taskId}: `} - - + {todos.current && todos.current.map((todo) => ( +
    • + {`Todo-${todo.taskId}: `} + + +
    • + ))} +
    + +
    + ); +} +``` +This implementation provide a basic pagination mechanism to navigate through the dataset and allows users to move to the next set of todo items or go back to the previous set. +{% /tabsitem %} +{% tabsitem #infinite-scroll-with-cursor title="Infinite Scroll with Cursor" %} +Update the Todos page in `src/pages/Todos.jsx`: +```js +import InfiniteScroll from '../components/InfiniteScroll'; +import { useTodos } from '../lib/context/todos'; + +export function Todos() { + const todos = useTodos(); + + return ( + +
      + {todos.current && + todos.current.map((todo) => ( +
    • + {`Task-${todo.taskId}: `} + +
    • ))}
    - -
    {tasks.currentPage}
    -
    + ); } -``` \ No newline at end of file +``` +This implementation will automatically load more tasks when the user scrolls to the bottom of the page, as long as there are more tasks to load (as indicated by the hasNext value from the context). +{% /tabsitem %} +{% /tabs %} + +# See it in action {% #see-it-in-action %} +Now's the perfect time to take a moment to run your application and see cursor pagination in action. Navigate through the tasks by scrolling or using the page navigation. As you scroll, you'll notice the seamless loading of todo items, thanks to infinite scrolling. Additionally, using the "Load Previous" and "Load More" buttons, you can manually navigate through tasks, showcasing the eficiency of cursor pagination.This approach ensures a smoother user experience by fetching only a subset of tod items, reducing server load and enhancing responsiveness. diff --git a/src/routes/docs/tutorials/pagination/step-9/+page.markdoc b/src/routes/docs/tutorials/pagination/step-9/+page.markdoc index 67090dfdcb..e92c0213b3 100644 --- a/src/routes/docs/tutorials/pagination/step-9/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-9/+page.markdoc @@ -1,39 +1,35 @@ --- layout: tutorial title: Trade-offs discussion -description: Learn about the trade-offs between each pagination method in a react app using appwrite backend. +description: Learn about the trade-offs between each pagination method in a React app using Appwrite backend. step: 9 --- - -# Pros and cons {% #pros-and-cons %} +# Undstanding pagination with Appwrite and React {% #understanding-pagination-with-appwrite-and-react %} +Pagination is a crucial feature for applications dealing with large datasets. It allows users to navigate through data in manageable chunks, ensuring that not all data is loaded at once, which can be inefficient and slow. In this section, we'll discuss the trade-offs between different pagination methods when using Appwrite and React. +# Comparing pagination methods {% #comparing-pagination-methods %} | Feature / Aspect| Offset Pagination | Cursor Pagination | |----------------|-----------------------|-----------------------| | Simplicity | High | Medium to Low | -| Performance | Good for shallow pagination, degrades with deep pagination |Consistently High | -| Data Consistency | Can be inconsistent with data changes | More consistent | -| Scalability | Good for smaller datasets | High for large datasets | +| Performance | Good for initial pages, degrades with deep pagination |Consistently High | +| Data Consistency | Can vary with data changes | More stable | +| Scalability | Good for smaller datasets | Excellent for large datasets | | Implementation Complexity | Low | Medium to High | | Direct Page Access | Yes | No | -Based on the application's requirements, you can then determine which features/aspects are most important and make a decision accordingly. - -# When to use each method {% #when-to-use-each-method %} +# Choosing the right pagination method {% #choosing-the-right-pagination-method %} -## Offset pagination {% #when-to-use-offset-pagination %} - * When you have a small dataset that doesn't change frequently. - * When you need to jump to specific page numbers. +* Offset pagination + * Ideal for small datasets or when the dataset doesn't change frequently. + * Useful when direct page access is required. * When simplicity and quick Implementation are priorities. -## Cursor pagination {% #when-to-use-cursor-pagination %} - * When dealing with large datasets - * For real-time applications or applications with frequently updating datasets. - * When deep pagination is necessary (i.e., users need to navigate to high page numbers). - * When performance and scalability are critical. +* Cursor pagination + * Best suited for large datasets or real-time applications. + * Maintains performance even with deep pagination + * Ensures more consistent data views, especially with frequently updating datasets. -# Recap {% #recap %} - * Offset pagination is straightforward and allow direct page access but can suffer from performance issues in deep pagination. - * Cursor pagination offers consistent high performance and is ideal for real-time or large datasets. - * Offset pagination causes data consistency issues, especially with changing datasets. - * Cursor pagination requires more work and doesn't enable page skips. - * For Appwrite, consider the dataset size, update frequency, and user navigation needs when choosing a pagination method. \ No newline at end of file +# Key Takeaways {% #key-takeaways %} + * Offset pagination is simple and allow direct page jumps but can suffer from performance issues in deep pagination. + * Cursor pagination, while requiring more initial setup, provides consistent performance and is ideal for real-time or large datasets. + * When using Appwrite with React, consider the dataset size, update frequency, and user navigation needs when choosing the best pagination method. \ No newline at end of file From 1eefb63445ee40ea1f454f67e8217f09517a1e32 Mon Sep 17 00:00:00 2001 From: timDeHof Date: Mon, 30 Oct 2023 10:06:38 -0400 Subject: [PATCH 04/10] docs(tutorials): update pagination tutorial with environment setup and context changes The tutorial has been updated to include a more detailed explanation of setting up the environment configuration for the project. This includes creating a `.env` file and adding the necessary environment variables for the project, database, and collection IDs. This change makes the tutorial more beginner-friendly and ensures that users understand the importance of environment configuration in a project. The tutorial also now includes changes to the context setup in the React application. Instead of using a single context for managing todos, two contexts are now created to manage todos for each pagination method. This change provides a clearer separation of concerns and makes the code easier to understand. The routing section of the tutorial has been updated to reflect these changes in context setup. The App function now returns the Header component along with other components wrapped in OffsetProvider and CursorProvider components. These providers are used to provide data to the components that they wrap. Finally, the tutorial now instructs users to create four separate files for the Todos pages. This change makes the tutorial more organized and easier to follow. --- .../tutorials/pagination/step-4/+page.markdoc | 9 +- .../tutorials/pagination/step-5/+page.markdoc | 42 +++--- .../tutorials/pagination/step-6/+page.markdoc | 122 +++++++++++++++--- 3 files changed, 130 insertions(+), 43 deletions(-) diff --git a/src/routes/docs/tutorials/pagination/step-4/+page.markdoc b/src/routes/docs/tutorials/pagination/step-4/+page.markdoc index c9822ce1b8..69b8bd0e6e 100644 --- a/src/routes/docs/tutorials/pagination/step-4/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-4/+page.markdoc @@ -24,7 +24,7 @@ When setting up your collection, make sure it has these attributes: * Max {% width=10 %} --- *   -* todoId +* taskId * Integer * Yes * - @@ -43,12 +43,15 @@ When setting up your collection, make sure it has these attributes: * completed * Boolean * Yes -* 2048 +* - * - * - {% /table %} # Add Permissions {% #add-permissions %} -1. Go to the 'Collection Settings' tab. +1. Go to the **Settings** tab. 2. Scroll to the 'Permissions' section. 3. Add the `Any` role, ensuring you select both `CREATE` and `READ` permissions. +{% info title="Tutorial only" %} + Don't set the **Any** role with both 'CREATE' and 'READ' in production. This is only for demonstration purposes for this tutorial. +{% /info %} \ No newline at end of file diff --git a/src/routes/docs/tutorials/pagination/step-5/+page.markdoc b/src/routes/docs/tutorials/pagination/step-5/+page.markdoc index 044d93509b..64cb8d75b5 100644 --- a/src/routes/docs/tutorials/pagination/step-5/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-5/+page.markdoc @@ -4,18 +4,30 @@ title: Environment setup description: Setup the environment for seeding the database step: 5 --- +# Create environment configuration file {% #create-env-file %} +Now, let's set up the environment configuration for our project. +Follow these steps to create a `.env` file in the root directory of the project. +1. Navigate to the root of the project using your terminal or file explorer. +2. Create a new file named `.env` (make sure that there is no file extension like `.txt`). +3. Open the `.env` file with your preferred text editor. +4. Add the necessary environment variables and their values for your respective project, database, and collection IDs in the format `KEY=VALUE`. Each variable should be on a new line. +```sh +APPWRITE_PROJECT_ID=[YOUR_PROJECT_ID] +APPWRITE_DATABASE_ID=[YOUR_DATABASE_ID] +APPWRITE_COLLECTION_ID=[YOUR_COLLECTION_ID] +``` +5. Save the file. +This `.env` file will now act as the central place for your project's environment-specific configurations. Make sure not to sure this file publicly, especially if it contains sensitive information like API keys or database credentials. # Seeding the collection {% #seeding-the-collection %} We'll now create a script that populates documents from a `setup.json` file.Create a new file `./db/setup.js` in the root of your project directory for this purpose. -Insert the following code, make sure to replace `[YOUR_DATABASE_ID]` and `[YOUR_COLLECTION_ID]` with your respective project and collection IDs. +Insert the following code, make sure that you have `[APPWRITE_DATABASE_ID]` and `[APPWRITE_COLLECTION_ID]` with your respective database and collection IDs in a .env file on the root. ```js import { databases } from "../src/lib/appwrite.js"; import { ID } from "appwrite"; -export const TODOS_DATABASE_ID = "[YOUR_DATABASE_ID]"; -export const TODOS_COLLECTION_ID = "[YOUR_COLLECTION_ID]"; const dataURL = "https://jsonplaceholder.typicode.com/users/1/todos"; async function seedCollection() { @@ -26,8 +38,8 @@ async function seedCollection() { for (let i = 0; i < data.length; i++) { const item = data[i]; let promise = await databases.createDocument( - TODOS_DATABASE_ID, - TODOS_COLLECTION_ID, + import.meta.env.APPWRITE_DATABASE_ID, + import.meta.env.APPWRITE_COLLECTION_ID, ID.unique(), { taskId: item.id, @@ -47,24 +59,12 @@ seedCollection(); ``` -# Add seed script to package.json {% #add-seed-script %} - -Open the project's package.json file, navigate to the `scripts` section, and add the following script: - -```sh -{ - ..., - "scripts": { - ... - "db:seed": "node ./db/setup.js" - }, -} -``` +# Run seed script {% #run-seed-script %} -Afterwards, open a terminal and execute: +Open a terminal and run the following command: ```sh -npm run db:seed +node ./db/setup.js ``` -You should see the process running in the terminal.Once it's completed, check your collection to verify that it's populated with the data. +You should see the process running in the terminal. Once it's completed, check your collection to verify that it's populated with the data. diff --git a/src/routes/docs/tutorials/pagination/step-6/+page.markdoc b/src/routes/docs/tutorials/pagination/step-6/+page.markdoc index 22507bc167..3df89902c8 100644 --- a/src/routes/docs/tutorials/pagination/step-6/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-6/+page.markdoc @@ -4,9 +4,9 @@ title: Adding todos description: Add todos to your React application using Appwrite. step: 6 --- -# Add todos context {% #add-todos-context %} +# Add todos contexts {% #add-todos-contexts %} -We'll now display our Todos on the page. In React, [context](https://reactjs.org/docs/context.html) allows us to share data across components. We'll leverage context and create a custom hook to manage our todos. +We'll now display our Todos on the page. In React, [context](https://reactjs.org/docs/context.html) allows us to share data across components. We'll leverage context and create two custom hooks to manage our todos. One for each pagination method. Create a new file named `src/lib/context/todos.jsx` and insert the following code: @@ -15,8 +15,45 @@ import { createContext, useContext, useEffect, useState } from "react"; import { databases } from "../appwrite"; import { Query } from "appwrite"; -const TODOS_DATABASE_ID = "[YOUR_DATABASE_ID]"; -const TODOS_COLLECTION_ID = "[YOUR_COLLECTION_ID]"; +const TodosContext = createContext(); + +export function useTodos() { + return useContext(TodosContext); +} + +export function OffsetProvider(props) { + const [todos, setTodos] = useState([]); + + async function init() { + try { + const response = await databases.listDocuments( + import.meta.env.VITE_APPWRITE_DATABASE_ID, + import.meta.env.VITE_APPWRITE_COLLECTION_ID, + [Query.orderAsc("$createdAt"), Query.limit(4)], + ); + setTodos(response.documents); + } catch (error) { + console.log(error); + } + } + + useEffect(() => { + init(); + }, []); + + return ( + + {props.children} + + ); +} +``` +Also create a new file named `src/lib/context/todos2.jsx` and insert the following code: + +```js +import { createContext, useContext, useEffect, useState } from "react"; +import { databases } from "../appwrite"; +import { Query } from "appwrite"; const TodosContext = createContext(); @@ -24,15 +61,15 @@ export function useTodos() { return useContext(TodosContext); } -export function TodosProvider(props) { +export function CursorProvider(props) { const [todos, setTodos] = useState([]); async function init() { try { const response = await databases.listDocuments( - TODOS_DATABASE_ID, - TODOS_COLLECTION_ID, - [Query.orderDesc("$createdAt"), Query.limit(4)], + import.meta.env.VITE_APPWRITE_DATABASE_ID, + import.meta.env.VITE_APPWRITE_COLLECTION_ID, + [Query.orderAsc("$createdAt"), Query.limit(4)], ); setTodos(response.documents); } catch (error) { @@ -51,24 +88,71 @@ export function TodosProvider(props) { ); } ``` -We'll expand on this context's functionality later. +We'll expand on these contexts' functionality later. ## Basic Routing {% #basic-routing %} - -First, enclose the `main` element with the `TodosProvider` component. +The todo app will have a header that displays links to the different pagination methods so you can select each method and view the example in the container below it. +The App function returns the Header component along with other components wrapped in OffsetProvider and CursorProvider components. These providers are used to provide data to the components that they wrap. Update `src/App.jsx` with the following code: ```js -import { TodosProvider } from "./lib/context/todos"; -import { Todos } from "./pages/Todos"; +import React from "react"; +import { OffsetProvider } from "./lib/context/todos"; +import { CursorProvider } from "./lib/context/todos2"; +import { TodosWithBasicPagination } from "./pages/Todos1"; +import { TodosWithBasicPaginationAndNumbers } from "./pages/Todos2"; +import { TodosWithBasicCursorPagination } from "./pages/Todos3"; +import { TodosWithBidirectionalPagination } from "./pages/Todos4"; +const Header = () => ( +
    + Basic Offset Pagination + + Offset Pagination With Page Numbers + + Basic Cursor Pagination + Bidirectional Pagination +
    +); function App() { + const showBasicOffsetPagination = () => { + if (window.location.pathname === "/basicOffsetPagination") { + return ; + } + }; + + const showOffsetPaginationWithPageNumbers = () => { + if (window.location.pathname === "/offsetPaginationWithPageNumbers") { + return ; + } + }; + + const showBasicCursorPagination = () => { + if (window.location.pathname === "/basicCursorPagination") { + return ; + } + }; + + const showBidirecionalPagination = () => { + if (window.location.pathname === "/bidirectionalPagination") { + return ; + } + }; + return (
    - - - +
    +
    + + {showBasicOffsetPagination()} + {showOffsetPaginationWithPageNumbers()} + + + {showBasicCursorPagination()} + {showBidirecionalPagination()} + +
    ); } @@ -78,9 +162,9 @@ export default App; ### Todos page {% #todos-page %} -Now, we can craft the Todos page, which will present users with a list of todos-a small chuck of the large dataset. +Now, we can craft our Todos pages, which will present users with a list of todos-a small chuck of the large dataset in three different ways. -Create a new file named `src/pages/Todos.jsx` and insert this placeholder code: +Create four new files in `src/pages/` folder, name them `Todos1`, `Todos2`, `Todos3` and `Todos4`. Then insert this placeholder code in each file: ```js import { useTodos } from "../lib/context/todos"; @@ -102,5 +186,5 @@ export function Todos() { ); } ``` - +we will expand these and add the UI next. The next steps will involve integrating offset or cursor pagination in our application. From 2c0b172517e357bbfccf5e20dd74c552702eb6a3 Mon Sep 17 00:00:00 2001 From: timDeHof Date: Mon, 30 Oct 2023 10:12:01 -0400 Subject: [PATCH 05/10] docs(tutorials): update pagination tutorial to improve clarity and flow The tutorial on pagination has been updated to improve the flow and clarity of the instructions. The changes include: - Replacing the instructions to create new files with instructions to update existing files. This change simplifies the tutorial and makes it easier to follow. - Adding more detailed explanations of the code changes. This helps readers understand the purpose of each change and how it contributes to the overall functionality of the application. - Removing redundant sections that were causing confusion. This makes the tutorial more concise and easier to understand. - Updating the context to use environment variables instead of hard-coded values. This makes the code more flexible and easier to configure. These changes were made to improve the quality of the tutorial and make it easier for readers to understand and follow. --- .../tutorials/pagination/step-7/+page.markdoc | 210 +++++++------- .../tutorials/pagination/step-8/+page.markdoc | 258 +++++++++--------- 2 files changed, 228 insertions(+), 240 deletions(-) diff --git a/src/routes/docs/tutorials/pagination/step-7/+page.markdoc b/src/routes/docs/tutorials/pagination/step-7/+page.markdoc index de897b8d34..fa9ca525ed 100644 --- a/src/routes/docs/tutorials/pagination/step-7/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-7/+page.markdoc @@ -25,34 +25,103 @@ this will retrieve the next four tasks, starting at task-5 We'll explore different UI patterns for offset pagination and how to implement them in our React app. -# Creating the Pagination Component {% #creating-the-pagination-component %} +# Adding the Pagination Component {% #Adding-the-pagination-component %} {% tabs %} {% tabsitem #Basic-pagination title="Basic Pagination" %} -Create a file and named `src/components/BasicPagination.jsx`, insert the following code: +Open the file named `src/pages/Todos1.jsx`, overwrite it with the following code: ```js -import React from 'react'; +import { useCallback } from "react"; +import { useTodos } from "../lib/context/todos"; function BasicPagination(props) { const { loadPrev, loadNext, hasNext, hasPrev } = props; + const debounce = (func, delay) => { + let debounceTimer; + return (...args) => { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => func(...args), delay); + }; + }; + + const debouncedLoadPrev = useCallback(debounce(loadPrev, 300), [loadPrev]); + + const debouncedLoadNext = useCallback(debounce(loadNext, 300), [loadNext]); + return (
    - - + +
    ); } -export default BasicPagination; +function TodosWithBasicPagination() { + const todos = useTodos(); + return ( +
    +

    Todos

    +
      + {todos.current.map((todo) => ( +
    • + {`Task-${todo.taskId}: `} + + +
    • + ))} +
    + 1} + /> +
    + ); +} +export default TodosWithBasicPagination; ``` This component will only have "Previous" and "Next" buttons. {% /tabsitem %} {% tabsitem #numbered-pagination title="Numbered Pagination" %} -Create a file and named `src/components/NumberedPagination.jsx`, insert the following code: +Open the file named `src/pages/Todos2.jsx`, overwrite it with the following code: ```js -import React from 'react'; +import { useCallback } from "react"; +import { useTodos } from "../lib/context/todos"; + +export function TodosWithBasicPaginationAndNumbers() { + const todos = useTodos(); + + return ( +
    +

    Todos

    +
      + {todos.current.map((todo) => ( +
    • + {`Task-${todo.taskId}: `} + + +
    • + ))} +
    + 1} + /> +
    + ); +} function NumberedPagination(props) { const { @@ -62,10 +131,10 @@ function NumberedPagination(props) { loadNext, loadPage, hasNext, - hasPrev + hasPrev, } = props; - const pages = []; + const pages = []; for (let i = 1; i <= totalPages; i++) { pages.push( @@ -74,17 +143,30 @@ function NumberedPagination(props) { , ); } + const debounce = (func, delay) => { + let debounceTimer; + return (...args) => { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => func(...args), delay); + }; + }; + + const debouncedLoadPrev = useCallback(debounce(loadPrev, 300), [loadPrev]); + + const debouncedLoadNext = useCallback(debounce(loadNext, 300), [loadNext]); return (
    - - {pages - + + {pages} +
    ); } - -export default NumberedPagination; ``` This component will have "Previous" and "Next" buttons along with numbered pages for direct navigation. @@ -93,32 +175,26 @@ This component will have "Previous" and "Next" buttons along with numbered pages # Update the todos context {% #update-the-todos-context %} -Update the contents of `src/lib/context/todos.jsx` with the following code, replacing `[YOUR_DATABASE_ID]` and `[YOUR_COLLECTION_ID]` with your project ID and collection ID. +Update the contents of `src/lib/context/todos.jsx` with the following code: ```js import { createContext, useContext, useEffect, useState } from "react"; import { databases } from "../appwrite"; import { Query } from "appwrite"; -const TODOS_DATABASE_ID = "[YOUR_DATABASE_ID]"; -const TODOS_COLLECTION_ID = "[YOUR_COLLECTION_ID]"; - const TodosContext = createContext(); export function useTodos() { return useContext(TodosContext); } -export function TodosProvider(props) { +export function OffsetProvider(props) { const [todos, setTodos] = useState([]); const [offset, setOffset] = useState(0); const [currentPage, setCurrentPage] = useState(1); - const [counts, setCounts] = useState({ - totalPages: 500, - totalResults: 1000, - }); + const [totalPages, setTotalPages] = useState(0); const pageLimit = 4; - const hasNext = counts.totalPages > currentPage; + const hasNext = totalPages > currentPage; async function loadNext() { if (hasNext) { @@ -139,8 +215,8 @@ export function TodosProvider(props) { async function init() { try { const response = await databases.listDocuments( - TODOS_DATABASE_ID, - TODOS_COLLECTION_ID, + import.meta.env.APPWRITE_DATABASE_ID, + import.meta.env.APPWRITE_COLLECTION_ID, [ Query.orderAsc("$createdAt"), Query.limit(pageLimit), @@ -150,10 +226,7 @@ export function TodosProvider(props) { setTodos(response.documents); setCurrentPage(Math.floor(offset / pageLimit) + 1); - setCounts({ - totalPages: response.total / pageLimit, - totalResults: response.total, - }); + setTotalPages(response.total / pageLimit); } catch (error) { console.log(error); } @@ -170,7 +243,7 @@ export function TodosProvider(props) { loadPrev, loadPage, currentPage, - totalPages: counts.totalPages, + totalPages, hasNext, hasPrev: currentPage > 1, }}> @@ -182,81 +255,10 @@ export function TodosProvider(props) { In this updated context: * We're using `Query.limit()` to set the number of tasks we want to fetch per page. * We're using `Query.offset()` to set the starting point for fetching tasks based on the current page. - * We've added `loadNextPage`, `loadPrevPage`, and `loadPage` function to handle pagination. + * We've added `loadNext`, `loadPrev`, and `loadPage` function to handle pagination. Now, you can use this context in your components to display todo items and handle pagination using Appwrite's offset pagination methods. -# Update the Todos page {% #update-the-todoss-page %} - -Now, let's use our Pagination component in the Todos page. -{% tabs %} -{% tabsitem #Basic-pagination title="Basic Pagination" %} -Update the Todos page in `src/pages/Todos.jsx`: -```js -import BasicPagination from '../components/BasicPagination'; -import {useTodos} from "../lib/context/todos"; -export function TodosWithBasicPagination() { - const todos = useTodos(); - - return ( -
    -

    Todos

    -
      - {todos.current.map(todo => ( -
    • - {`Task-${todo.taskId}: `} - - -
    • - ))} -
    - 1} - /> -
    - ); -} -``` -{% /tabsitem %} -{% tabsitem #numbered-pagination title="Numbered Pagination" %} -Update the Todos page in `src/pages/Todos.jsx`: -```js -import NumberedPagination from '../components/NumberedPagination'; - -export function TasksWithNumberedPagination() { - const tasks = useTasks(); - - return ( -
    -

    Todos

    -
      - {tasks.current.map(task => ( -
    • - {`Task-${task.taskId}: `} - - -
    • - ))} -
    - 1} - /> -
    - ); -} -``` -{% /tabsitem %} -{% /tabs %} - You can now choose between the two offset pagination styles based on your UI/UX preferences. In the next step, we'll transition from our current pagination method to cursor pagination method. diff --git a/src/routes/docs/tutorials/pagination/step-8/+page.markdoc b/src/routes/docs/tutorials/pagination/step-8/+page.markdoc index fea4ad9cb6..868db95aa4 100644 --- a/src/routes/docs/tutorials/pagination/step-8/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-8/+page.markdoc @@ -27,161 +27,210 @@ Cursor pagination is typically used in scenarios where the dataset is large, and {% tabs %} {% tabsitem #Basic-cursor-pagination title="Basic Cursor Pagination" %} -Create a file and named `src/components/BasicCursorPagination.jsx`, insert the following code: +Open the file named `src/pages/Todos3.jsx`, overwrite the content with the following code: ```js +import React, { useCallback } from "react"; +import { useTodos } from "../lib/context/todos2"; +export function TodosWithBasicCursorPagination() { + const todos = useTodos(); + + return ( +
    +

    Todos

    +
      + {todos.current && + todos.current.map((todo) => ( +
    • + {`Todo-${todo.taskId}: `} + + +
    • + ))} +
    + +
    + ); +} function CursorPagination(props) { - const { - hasNext, - loadMore, - loadPrev, - currentPage - } = props; + const { hasMore, loadMore, currentPage } = props; + const debounce = (func, delay) => { + let debounceTimer; + return (...args) => { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => func(...args), delay); + }; + }; + + const debouncedLoadMore = useCallback(debounce(loadMore, 300), [loadMore]); return (
    - - Page {currentPage} -
    ); } -export default CursorPagination; - ``` -This is the most straightforward use of cursor pagination. You have "Load Previous" and "Load More" buttons, and you fetch data based on the last or first items's cursor. +This is the most straightforward use of cursor pagination. You have "Load More" button,which will fetch more data based on the last item's cursor. {% /tabsitem %} -{% tabsitem #infinite-scroll-with-cursor title="Infinite Scroll with Cursor" %} -Create a file and named `src/components/InfiniteScroll.jsx`, insert the following code: +{% tabsitem #Bidirectional-cursor-pagination title="Bidirectional Cursor Pagination" %} +Open the file named `src/pages/Todos4.jsx`, overwrite the content with the following code: ```js -import React, { useEffect } from 'react'; +import React, { useCallback } from "react"; +import { useTodos } from "../lib/context/todos2"; -function InfiniteScroll(props) { - const { - hasNext, - loadMore, - current - } = props; +export function TodosWithBidirectionalPagination() { + const todos = useTodos(); - useEffect(() => { - const handleScroll = () => { - // Check if we're at the bottom of the viewport - if (window.innerHeight + document.documentElement.scrollTop !== document.documentElement.offsetHeight) return; - if (hasNext && current && current.length) { - loadMore(); - } - }; + return ( +
    +

    Todos

    +
      + {todos.current && + todos.current.map((todo) => ( +
    • + {`Todo-${todo.taskId}: `} + + +
    • + ))} +
    + +
    + ); +} +function BidirectionalPagination(props) { + const { hasMore, loadMore, loadPrev, currentPage } = props; - window.addEventListener('scroll', handleScroll); - return () => window.removeEventListener('scroll', handleScroll); - }, [hasNext, current]); + const debounce = (func, delay) => { + let debounceTimer; + return (...args) => { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => func(...args), delay); + }; + }; + const debouncedLoadMore = useCallback(debounce(loadMore, 300), [loadMore]); + const debouncedLoadPrev = useCallback(debounce(loadPrev, 300), [loadPrev]); return (
    - {props.children} - {hasNext &&
    Loading more tasks...
    } + + Page {currentPage} +
    ); } - -export default InfiniteScroll; ``` -Instead of manually clicking a "load more" button, you can automatically fetch more items when the user scrolls to the bottom of the list. This provides a seamless experience for users. +Bidirectional cursor pagination allows users to navigate through datasets both forwards and backwards using a cursor to maintain position, providing greater flexibility in exploring data. On the other hand, regular cursor pagination typically only supports forward navigation through the dataset, limiting the user's ability to easily access or revisit previous data entries. {% /tabsitem %} {% /tabs %} # Update todos context {% #updating-todos-context %} -Update the contents of `src/lib/context/todos.jsx` with the following code, replacing `[YOUR_DATABASE_ID]` and `[YOUR_COLLECTION_ID]` with your project ID and collection ID. +Update the contents of `src/lib/context/todos2.jsx` with the following code: ```js import { createContext, useContext, useEffect, useState } from "react"; import { databases } from "../appwrite"; import { Query } from "appwrite"; -const TODOS_DATABASE_ID = "[YOUR_DATABASE_ID]"; -const TODOS_COLLECTION_ID = "[YOUR_COLLECTION_ID]"; - const TodosContext = createContext(); export function useTodos() { return useContext(TodosContext); } -export function TodosProvider(props) { +export function TodosProvider2(props) { const [todos, setTodos] = useState([]); const [lastId, setLastId] = useState(null); const [firstId, setFirstId] = useState(null); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(0); + const [loading, setLoading] = useState(false); const pageLimit = 4; - const hasNext = totalPages > currentPage; + const hasMore = totalPages > currentPage; async function loadMore() { - if (currentPage < totalPages) { - try { + if (todos.length === 0 || (hasMore && !loading)) { + setLoading(true); + try { const response = await databases.listDocuments( - TODOS_DATABASE_ID, - TODOS_COLLECTION_ID, + import.meta.env.APPWRITE_DATABASE_ID, + import.meta.env.APPWRITE_COLLECTION_ID, [ - Query.orderDesc("$createdAt"), + Query.orderAsc("$createdAt"), Query.limit(pageLimit), Query.cursorAfter(lastId), ], ); - setCurrentPage(currentPage + 1) + setCurrentPage((prevPage) => prevPage + 1); setFirstId(response.documents[0].$id); setLastId(response.documents[response.documents.length - 1].$id); - setTodos([...todos, ...response.documents]); - } catch (error) { - console.log("Error loading more todos:", error); - } + setTodos((prevTodos) => [...prevTodos, ...response.documents]); + } catch (error) { + console.log("Error loading more todos:", error); + } + setLoading(false); } } + async function loadPrev() { - if (currentPage > 1) { - try { + if (currentPage > 1) { + try { const response = await databases.listDocuments( - TODOS_DATABASE_ID, - TODOS_COLLECTION_ID, + import.meta.env.APPWRITE_DATABASE_ID, + import.meta.env.APPWRITE_COLLECTION_ID, [ Query.orderAsc("$createdAt"), Query.limit(pageLimit), Query.cursorBefore(firstId), ], ); - setCurrentPage(currentPage -1 ); + setCurrentPage(currentPage - 1); setFirstId(response.documents[0].$id); setLastId(response.documents[response.documents.length - 1].$id); - setTasks(response.documents); - } catch (error) { - console.error("Error loading previous todos:", error); + setTodos(response.documents); + } catch (error) { + console.error("Error loading previous todos:", error); + } } } - } async function init() { + setLoading(true); try { const response = await databases.listDocuments( - TODOS_DATABASE_ID, - TODOS_COLLECTION_ID, + import.meta.env.APPWRITE_DATABASE_ID, + import.meta.env.APPWRITE_COLLECTION_ID, [Query.orderAsc("$createdAt"), Query.limit(pageLimit)], ); setFirstId(response.documents[0].$id); setLastId(response.documents[response.documents.length - 1].$id); - setTasks(response.documents); + setTodos(response.documents); setTotalPages(Math.ceil(response.total / pageLimit)); } catch (error) { console.log("Error initializing tasks:", error); } + setLoading(false); } useEffect(() => { init(); - }, []); + }, [pageLimit]); return ( {props.children} ); } + ``` In this updated context: * We're using `Query.cursorAfter()` is used to fetch documents that come after a specific document. @@ -205,71 +256,6 @@ In this updated context: * We's added `firstId` to hold the ID of the first document in the current set of todos. By using this ID with `Query.cursorBefore()`, we're telling Appwrite: "Give me the set of todos that come before this specific todo item." Now, you can use this context in your components to display todo items and handle pagination using Appwrite's cursor pagination methods. -# Update Todos page {% #update-todos-page %} -Now, let's use our Pagination component in the Todos Page. -{% tabs %} -{% tabsitem #Basic-cursor-pagination title="Basic Cursor Pagination" %} -Update the Todos page in `src/pages/Todos.jsx`: -```js -import React from 'react'; -import { useTodos } from '../lib/context/todos'; -import BasicCursorPagination from '../components/BasicCursorPagination'; - -export function TodosPage() { - const todos = useTodos(); - - return ( -
    -

    Todos

    -
      - {todos.current && todos.current.map((todo) => ( -
    • - {`Todo-${todo.taskId}: `} - - -
    • - ))} -
    - -
    - ); -} -``` -This implementation provide a basic pagination mechanism to navigate through the dataset and allows users to move to the next set of todo items or go back to the previous set. -{% /tabsitem %} -{% tabsitem #infinite-scroll-with-cursor title="Infinite Scroll with Cursor" %} -Update the Todos page in `src/pages/Todos.jsx`: -```js -import InfiniteScroll from '../components/InfiniteScroll'; -import { useTodos } from '../lib/context/todos'; - -export function Todos() { - const todos = useTodos(); - - return ( - -
      - {todos.current && - todos.current.map((todo) => ( -
    • - {`Task-${todo.taskId}: `} - - -
    • - ))} -
    -
    - ); -} -``` -This implementation will automatically load more tasks when the user scrolls to the bottom of the page, as long as there are more tasks to load (as indicated by the hasNext value from the context). -{% /tabsitem %} -{% /tabs %} # See it in action {% #see-it-in-action %} -Now's the perfect time to take a moment to run your application and see cursor pagination in action. Navigate through the tasks by scrolling or using the page navigation. As you scroll, you'll notice the seamless loading of todo items, thanks to infinite scrolling. Additionally, using the "Load Previous" and "Load More" buttons, you can manually navigate through tasks, showcasing the eficiency of cursor pagination.This approach ensures a smoother user experience by fetching only a subset of tod items, reducing server load and enhancing responsiveness. +Now's the perfect time to take a moment to run your application and see cursor pagination in action. Navigate using the "Load Previous" and "Load More" buttons, you can manually navigate through tasks, showcasing the efficiency of cursor pagination.This approach ensures a smoother user experience by fetching only a subset of todo items, reducing server load and enhancing responsiveness. From a0a4a68948e9644aada7df1fd7b40e5c6ed25373 Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Mon, 30 Oct 2023 15:17:11 -0400 Subject: [PATCH 06/10] Apply suggestions from code review --- .../docs/tutorials/pagination/step-2/+page.markdoc | 2 +- .../docs/tutorials/pagination/step-4/+page.markdoc | 8 ++++---- .../docs/tutorials/pagination/step-5/+page.markdoc | 14 +++++--------- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/routes/docs/tutorials/pagination/step-2/+page.markdoc b/src/routes/docs/tutorials/pagination/step-2/+page.markdoc index ceba13e534..d3b486bdba 100644 --- a/src/routes/docs/tutorials/pagination/step-2/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-2/+page.markdoc @@ -18,7 +18,7 @@ npm create vite@latest todo-app -- --template react && cd todo-app Install the JavaScript Appwrite Web SDK. ```sh -npm install appwrite +npm install appwrite dotenv ``` You can start the development server to watch your app update in the browser as you make changes. diff --git a/src/routes/docs/tutorials/pagination/step-4/+page.markdoc b/src/routes/docs/tutorials/pagination/step-4/+page.markdoc index 69b8bd0e6e..42afc2cbc3 100644 --- a/src/routes/docs/tutorials/pagination/step-4/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-4/+page.markdoc @@ -50,8 +50,8 @@ When setting up your collection, make sure it has these attributes: # Add Permissions {% #add-permissions %} 1. Go to the **Settings** tab. -2. Scroll to the 'Permissions' section. -3. Add the `Any` role, ensuring you select both `CREATE` and `READ` permissions. -{% info title="Tutorial only" %} - Don't set the **Any** role with both 'CREATE' and 'READ' in production. This is only for demonstration purposes for this tutorial. +2. Scroll to the **Permissions** section. +3. Add the **Any** role, ensuring you select both **CREATE** and **READ** permissions. +{% info title="Any role" %} + The **Any** role is used in this tutorial for demonstration purposes. Remember to protect your user's data in production applications by configuring proper [permissions](/docs/advanced/platform/permissions). {% /info %} \ No newline at end of file diff --git a/src/routes/docs/tutorials/pagination/step-5/+page.markdoc b/src/routes/docs/tutorials/pagination/step-5/+page.markdoc index 64cb8d75b5..04a2727748 100644 --- a/src/routes/docs/tutorials/pagination/step-5/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-5/+page.markdoc @@ -5,28 +5,24 @@ description: Setup the environment for seeding the database step: 5 --- # Create environment configuration file {% #create-env-file %} -Now, let's set up the environment configuration for our project. -Follow these steps to create a `.env` file in the root directory of the project. -1. Navigate to the root of the project using your terminal or file explorer. -2. Create a new file named `.env` (make sure that there is no file extension like `.txt`). -3. Open the `.env` file with your preferred text editor. -4. Add the necessary environment variables and their values for your respective project, database, and collection IDs in the format `KEY=VALUE`. Each variable should be on a new line. +Set up the environment configuration for your project to declare project-wide variables like Appwrite project ID, database ID, and collection ID. Create a `.env` and add the declare variables. ```sh APPWRITE_PROJECT_ID=[YOUR_PROJECT_ID] APPWRITE_DATABASE_ID=[YOUR_DATABASE_ID] APPWRITE_COLLECTION_ID=[YOUR_COLLECTION_ID] -``` -5. Save the file. This `.env` file will now act as the central place for your project's environment-specific configurations. Make sure not to sure this file publicly, especially if it contains sensitive information like API keys or database credentials. # Seeding the collection {% #seeding-the-collection %} We'll now create a script that populates documents from a `setup.json` file.Create a new file `./db/setup.js` in the root of your project directory for this purpose. -Insert the following code, make sure that you have `[APPWRITE_DATABASE_ID]` and `[APPWRITE_COLLECTION_ID]` with your respective database and collection IDs in a .env file on the root. +Insert the following code, make sure that you have `[APPWRITE_DATABASE_ID]` and `[APPWRITE_COLLECTION_ID]` with your respective database and collection IDs in a `.env` file on the root. ```js import { databases } from "../src/lib/appwrite.js"; import { ID } from "appwrite"; +import dotenv from "dotenv"; + +dotenv.config(); const dataURL = "https://jsonplaceholder.typicode.com/users/1/todos"; From f6999c653ff7868f62b60732205f3d128351825c Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Mon, 30 Oct 2023 15:26:18 -0400 Subject: [PATCH 07/10] Apply suggestions from code review --- .../tutorials/pagination/step-6/+page.markdoc | 18 +++++++++--------- .../tutorials/pagination/step-7/+page.markdoc | 4 ++-- .../tutorials/pagination/step-8/+page.markdoc | 10 +++++----- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/routes/docs/tutorials/pagination/step-6/+page.markdoc b/src/routes/docs/tutorials/pagination/step-6/+page.markdoc index 3df89902c8..e6279ecaf7 100644 --- a/src/routes/docs/tutorials/pagination/step-6/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-6/+page.markdoc @@ -8,7 +8,7 @@ step: 6 We'll now display our Todos on the page. In React, [context](https://reactjs.org/docs/context.html) allows us to share data across components. We'll leverage context and create two custom hooks to manage our todos. One for each pagination method. -Create a new file named `src/lib/context/todos.jsx` and insert the following code: +Create a new file named `src/lib/context/todos-offset-pagination.jsx` and insert the following code: ```js import { createContext, useContext, useEffect, useState } from "react"; @@ -48,7 +48,7 @@ export function OffsetProvider(props) { ); } ``` -Also create a new file named `src/lib/context/todos2.jsx` and insert the following code: +Also create a new file named `src/lib/context/todos-cursor-pagination.jsx` and insert the following code: ```js import { createContext, useContext, useEffect, useState } from "react"; @@ -98,12 +98,12 @@ Update `src/App.jsx` with the following code: ```js import React from "react"; -import { OffsetProvider } from "./lib/context/todos"; -import { CursorProvider } from "./lib/context/todos2"; -import { TodosWithBasicPagination } from "./pages/Todos1"; -import { TodosWithBasicPaginationAndNumbers } from "./pages/Todos2"; -import { TodosWithBasicCursorPagination } from "./pages/Todos3"; -import { TodosWithBidirectionalPagination } from "./pages/Todos4"; +import { OffsetProvider } from "./lib/context/todos-offset-pagination"; +import { CursorProvider } from "./lib/context/todos-cursor-pagination"; +import { TodosWithBasicPagination } from "./pages/TodosWithBasicPagination"; +import { TodosWithBasicPaginationAndNumbers } from "./pages/TodosWithBasicPaginationAndNumbers"; +import { TodosWithBasicCursorPagination } from "./pages/TodosWithBasicCursorPagination"; +import { TodosWithBidirectionalPagination } from "./pages/TodosWithBidirectionalPagination"; const Header = () => (
    Basic Offset Pagination @@ -164,7 +164,7 @@ export default App; Now, we can craft our Todos pages, which will present users with a list of todos-a small chuck of the large dataset in three different ways. -Create four new files in `src/pages/` folder, name them `Todos1`, `Todos2`, `Todos3` and `Todos4`. Then insert this placeholder code in each file: +Create four new files in `src/pages/` folder, name them `TodosWithBasicPagination`, `TodosWithBasicPaginationAndNumbers`, `TodosWithBasicCursorPagination`, and `TodosWithBidirectionalPagination `. Then insert this placeholder code in each file: ```js import { useTodos } from "../lib/context/todos"; diff --git a/src/routes/docs/tutorials/pagination/step-7/+page.markdoc b/src/routes/docs/tutorials/pagination/step-7/+page.markdoc index fa9ca525ed..f7ab7c1bcf 100644 --- a/src/routes/docs/tutorials/pagination/step-7/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-7/+page.markdoc @@ -29,7 +29,7 @@ We'll explore different UI patterns for offset pagination and how to implement t {% tabs %} {% tabsitem #Basic-pagination title="Basic Pagination" %} -Open the file named `src/pages/Todos1.jsx`, overwrite it with the following code: +Open the file named `src/pages/TodosWithBasicPagination.jsx`, overwrite it with the following code: ```js import { useCallback } from "react"; import { useTodos } from "../lib/context/todos"; @@ -90,7 +90,7 @@ export default TodosWithBasicPagination; This component will only have "Previous" and "Next" buttons. {% /tabsitem %} {% tabsitem #numbered-pagination title="Numbered Pagination" %} -Open the file named `src/pages/Todos2.jsx`, overwrite it with the following code: +Open the file named `src/pages/TodosWithBasicPaginationAndNumbers.jsx`, overwrite it with the following code: ```js import { useCallback } from "react"; import { useTodos } from "../lib/context/todos"; diff --git a/src/routes/docs/tutorials/pagination/step-8/+page.markdoc b/src/routes/docs/tutorials/pagination/step-8/+page.markdoc index 868db95aa4..98af1acfe2 100644 --- a/src/routes/docs/tutorials/pagination/step-8/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-8/+page.markdoc @@ -27,10 +27,10 @@ Cursor pagination is typically used in scenarios where the dataset is large, and {% tabs %} {% tabsitem #Basic-cursor-pagination title="Basic Cursor Pagination" %} -Open the file named `src/pages/Todos3.jsx`, overwrite the content with the following code: +Open the file named `src/pages/TodosWithBasicCursorPagination.jsx`, overwrite the content with the following code: ```js import React, { useCallback } from "react"; -import { useTodos } from "../lib/context/todos2"; +import { useTodos } from "../lib/context/todos-cursor-pagination"; export function TodosWithBasicCursorPagination() { const todos = useTodos(); @@ -81,10 +81,10 @@ function CursorPagination(props) { This is the most straightforward use of cursor pagination. You have "Load More" button,which will fetch more data based on the last item's cursor. {% /tabsitem %} {% tabsitem #Bidirectional-cursor-pagination title="Bidirectional Cursor Pagination" %} -Open the file named `src/pages/Todos4.jsx`, overwrite the content with the following code: +Open the file named `src/pages/TodosWithBidirectionalPagination.jsx`, overwrite the content with the following code: ```js import React, { useCallback } from "react"; -import { useTodos } from "../lib/context/todos2"; +import { useTodos } from "../lib/context/todos-cursor-pagination"; export function TodosWithBidirectionalPagination() { const todos = useTodos(); @@ -143,7 +143,7 @@ Bidirectional cursor pagination allows users to navigate through datasets both f # Update todos context {% #updating-todos-context %} -Update the contents of `src/lib/context/todos2.jsx` with the following code: +Update the contents of `src/lib/context/todos-cursor-pagination.jsx` with the following code: ```js import { createContext, useContext, useEffect, useState } from "react"; From 55b349114c56d004eaf594062a1547b31f95accd Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Mon, 30 Oct 2023 15:27:58 -0400 Subject: [PATCH 08/10] Apply suggestions from code review --- src/routes/docs/tutorials/pagination/step-10/+page.markdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/docs/tutorials/pagination/step-10/+page.markdoc b/src/routes/docs/tutorials/pagination/step-10/+page.markdoc index 5b7137a432..c7648bf6ed 100644 --- a/src/routes/docs/tutorials/pagination/step-10/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-10/+page.markdoc @@ -20,4 +20,4 @@ Before deploying or integrating your pagination into a larger project, it's cruc To deepen your understanding and explore more advanced pagination techniques or nuances, refer to the official documentation: [Appwrite pagination documentation](/docs/products/databases/pagination). -Remember, the choice between offset and cursor pagination will depend on your application's specific needs. Always consider the size of the dataset, usr experience, and performance requirements when making your decision. \ No newline at end of file +Remember, the choice between offset and cursor pagination will depend on your application's specific needs. Always consider the size of the dataset, user experience, and performance requirements when making your decision. \ No newline at end of file From e1105cba842b11434f8490ab68acf0599ac5c84e Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Mon, 30 Oct 2023 15:29:37 -0400 Subject: [PATCH 09/10] Update src/routes/docs/tutorials/pagination/step-5/+page.markdoc --- src/routes/docs/tutorials/pagination/step-5/+page.markdoc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/docs/tutorials/pagination/step-5/+page.markdoc b/src/routes/docs/tutorials/pagination/step-5/+page.markdoc index 04a2727748..7d9b06c782 100644 --- a/src/routes/docs/tutorials/pagination/step-5/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-5/+page.markdoc @@ -10,6 +10,7 @@ Set up the environment configuration for your project to declare project-wide va APPWRITE_PROJECT_ID=[YOUR_PROJECT_ID] APPWRITE_DATABASE_ID=[YOUR_DATABASE_ID] APPWRITE_COLLECTION_ID=[YOUR_COLLECTION_ID] +``` This `.env` file will now act as the central place for your project's environment-specific configurations. Make sure not to sure this file publicly, especially if it contains sensitive information like API keys or database credentials. # Seeding the collection {% #seeding-the-collection %} From 95391d9b5ef478dd3858f8b663b22666bdf57014 Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Mon, 30 Oct 2023 16:03:23 -0400 Subject: [PATCH 10/10] Apply suggestions from code review --- .../docs/tutorials/pagination/step-6/+page.markdoc | 14 +++++++------- .../docs/tutorials/pagination/step-7/+page.markdoc | 10 +++++----- .../docs/tutorials/pagination/step-8/+page.markdoc | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/routes/docs/tutorials/pagination/step-6/+page.markdoc b/src/routes/docs/tutorials/pagination/step-6/+page.markdoc index e6279ecaf7..07c2e378fd 100644 --- a/src/routes/docs/tutorials/pagination/step-6/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-6/+page.markdoc @@ -100,10 +100,10 @@ Update `src/App.jsx` with the following code: import React from "react"; import { OffsetProvider } from "./lib/context/todos-offset-pagination"; import { CursorProvider } from "./lib/context/todos-cursor-pagination"; -import { TodosWithBasicPagination } from "./pages/TodosWithBasicPagination"; -import { TodosWithBasicPaginationAndNumbers } from "./pages/TodosWithBasicPaginationAndNumbers"; +import { TodosWithOffsetPagination } from "./pages/TodosWithOffsetPagination"; +import { TodosWithOffsetPaginationAndNumbers } from "./pages/TodosWithOffsetPaginationAndNumbers"; import { TodosWithBasicCursorPagination } from "./pages/TodosWithBasicCursorPagination"; -import { TodosWithBidirectionalPagination } from "./pages/TodosWithBidirectionalPagination"; +import { TodosWithBidirectionalCursorPagination } from "./pages/TodosWithBidirectionalCursorPagination"; const Header = () => (
    Basic Offset Pagination @@ -118,13 +118,13 @@ const Header = () => ( function App() { const showBasicOffsetPagination = () => { if (window.location.pathname === "/basicOffsetPagination") { - return ; + return ; } }; const showOffsetPaginationWithPageNumbers = () => { if (window.location.pathname === "/offsetPaginationWithPageNumbers") { - return ; + return ; } }; @@ -136,7 +136,7 @@ function App() { const showBidirecionalPagination = () => { if (window.location.pathname === "/bidirectionalPagination") { - return ; + return ; } }; @@ -164,7 +164,7 @@ export default App; Now, we can craft our Todos pages, which will present users with a list of todos-a small chuck of the large dataset in three different ways. -Create four new files in `src/pages/` folder, name them `TodosWithBasicPagination`, `TodosWithBasicPaginationAndNumbers`, `TodosWithBasicCursorPagination`, and `TodosWithBidirectionalPagination `. Then insert this placeholder code in each file: +Create four new files in `src/pages/` folder, name them `TodosWithOffsetPagination`, `TodosWithOffsetPaginationAndNumbers`, `TodosWithBasicCursorPagination`, and `TodosWithBidirectionalCursorPagination `. Then insert this placeholder code in each file: ```js import { useTodos } from "../lib/context/todos"; diff --git a/src/routes/docs/tutorials/pagination/step-7/+page.markdoc b/src/routes/docs/tutorials/pagination/step-7/+page.markdoc index f7ab7c1bcf..2db6c4f88e 100644 --- a/src/routes/docs/tutorials/pagination/step-7/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-7/+page.markdoc @@ -29,7 +29,7 @@ We'll explore different UI patterns for offset pagination and how to implement t {% tabs %} {% tabsitem #Basic-pagination title="Basic Pagination" %} -Open the file named `src/pages/TodosWithBasicPagination.jsx`, overwrite it with the following code: +Open the file named `src/pages/TodosWithOffsetPagination.jsx`, overwrite it with the following code: ```js import { useCallback } from "react"; import { useTodos } from "../lib/context/todos"; @@ -61,7 +61,7 @@ function BasicPagination(props) { ); } -function TodosWithBasicPagination() { +function TodosWithOffsetPagination() { const todos = useTodos(); return ( @@ -85,17 +85,17 @@ function TodosWithBasicPagination() { ); } -export default TodosWithBasicPagination; +export default TodosWithOffsetPagination; ``` This component will only have "Previous" and "Next" buttons. {% /tabsitem %} {% tabsitem #numbered-pagination title="Numbered Pagination" %} -Open the file named `src/pages/TodosWithBasicPaginationAndNumbers.jsx`, overwrite it with the following code: +Open the file named `src/pages/TodosWithOffsetPaginationAndNumbers.jsx`, overwrite it with the following code: ```js import { useCallback } from "react"; import { useTodos } from "../lib/context/todos"; -export function TodosWithBasicPaginationAndNumbers() { +export function TodosWithOffsetPaginationAndNumbers() { const todos = useTodos(); return ( diff --git a/src/routes/docs/tutorials/pagination/step-8/+page.markdoc b/src/routes/docs/tutorials/pagination/step-8/+page.markdoc index 98af1acfe2..c75f43f252 100644 --- a/src/routes/docs/tutorials/pagination/step-8/+page.markdoc +++ b/src/routes/docs/tutorials/pagination/step-8/+page.markdoc @@ -81,12 +81,12 @@ function CursorPagination(props) { This is the most straightforward use of cursor pagination. You have "Load More" button,which will fetch more data based on the last item's cursor. {% /tabsitem %} {% tabsitem #Bidirectional-cursor-pagination title="Bidirectional Cursor Pagination" %} -Open the file named `src/pages/TodosWithBidirectionalPagination.jsx`, overwrite the content with the following code: +Open the file named `src/pages/TodosWithBidirectionalCursorPagination.jsx`, overwrite the content with the following code: ```js import React, { useCallback } from "react"; import { useTodos } from "../lib/context/todos-cursor-pagination"; -export function TodosWithBidirectionalPagination() { +export function TodosWithBidirectionalCursorPagination() { const todos = useTodos(); return (