{"id":22269101,"url":"https://github.com/jason89521/daxus","last_synced_at":"2025-06-23T15:33:49.848Z","repository":{"id":157139220,"uuid":"626313411","full_name":"jason89521/daxus","owner":"jason89521","description":"Daxus is a server state management library for React that provides full control over data, leading to a better user experience.","archived":false,"fork":false,"pushed_at":"2024-10-14T07:37:58.000Z","size":2367,"stargazers_count":96,"open_issues_count":30,"forks_count":1,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-06-05T07:36:51.028Z","etag":null,"topics":["cache","data","dedupe","hook","react","revalidate","server-state-management","user-experience"],"latest_commit_sha":null,"homepage":"https://daxus-simple-forum.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jason89521.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-04-11T08:09:01.000Z","updated_at":"2025-05-20T10:30:26.000Z","dependencies_parsed_at":"2023-12-18T07:24:04.448Z","dependency_job_id":"5d083370-4bfc-42bd-9832-a132a3e45401","html_url":"https://github.com/jason89521/daxus","commit_stats":{"total_commits":606,"total_committers":3,"mean_commits":202.0,"dds":0.3712871287128713,"last_synced_commit":"c8f433f5298083229ed733434af07cec82448b8a"},"previous_names":["jason89521/react-server-model"],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/jason89521/daxus","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jason89521%2Fdaxus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jason89521%2Fdaxus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jason89521%2Fdaxus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jason89521%2Fdaxus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jason89521","download_url":"https://codeload.github.com/jason89521/daxus/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jason89521%2Fdaxus/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260884630,"owners_count":23076887,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","data","dedupe","hook","react","revalidate","server-state-management","user-experience"],"created_at":"2024-12-03T11:15:07.842Z","updated_at":"2025-06-23T15:33:49.817Z","avatar_url":"https://github.com/jason89521.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Daxus\n\n[![npm version][npm-image]][npm-url]\n[![License][license-image]][license-url]\n[![PR's Welcome][pr-welcoming-image]][pr-welcoming-url]\n[![Test coverage][codecov-image]][codecov-url]\n\nDaxus is a server state management library for React that provides full control over data, leading to a better user experience.\n\n- Customizable data structure\n- Auto deduplication\n- Revalidate on Focus\n- Revalidate on network reconnection\n- Polling support\n- Pre-built pagination adapter\n- Easy mutation\n- Written in Typescript\n\n---\n\n- [Comparison](#comparison)\n- [Installation](#installation)\n- [Simple Example](#simple-example)\n  - [Other Examples](#other-examples)\n- [Tutorial](#tutorial)\n  - [Pagination Data](#pagination-data)\n  - [Auto Model](#auto-model)\n  - [Invalidation](#invalidation)\n- [Documents](#documents)\n- [Development Motivation](#development-motivation)\n  - [Why not use React Query?](#why-not-use-react-query)\n  - [Goals to achieve](#goals-to-achieve)\n- [Design Philosophy](#design-philosophy)\n\n## Comparison\n\n[The difference with React Query](./docs/the-difference-with-RQ.md)\n\n|                             | `Daxus` | `React Query` | `Redux With Async Thunk` |\n| --------------------------- | :-----: | :-----------: | :----------------------: |\n| Customizable data structure |   ✅    |      ❌       |            ✅            |\n| Dedupe                      |   ✅    |      ✅       |            ❌            |\n| Revalidate on focus         |   ✅    |      ✅       |            ❌            |\n| Revalidate on reconnect     |   ✅    |      ✅       |            ❌            |\n| Revalidate if stale         |   ✅    |      ✅       |            ❌            |\n| Polling                     |   ✅    |      ✅       |            ❌            |\n| Error retry                 |   ✅    |      ✅       |            ❌            |\n| Invalidate queries          |   ✅    |      ✅       |            ❌            |\n| Mutation                    |   ✅    |      ✅       |            ✅            |\n| Conditional fetching        |   ✅    |      ✅       |            ❌            |\n| DevTool                     |   ❌    |      ✅       |            ✅            |\n\n## Installation\n\n```sh\npnpm add daxus\n```\n\n```sh\nyarn add daxus\n```\n\n```sh\nnpm install daxus\n```\n\n## Simple Example\n\n```typescript\nimport { createModel, createPaginationAdapter, useAccessor } from 'daxus';\n\n// You don't have to use createPaginationAdapter specifically.\n// You can use any data structure that meets your requirements.\nexport const postAdapter = createPaginationAdapter\u003cPost\u003e();\nexport const postModel = createModel({\n  initialState: postAdapter.getInitialState(),\n});\nexport const getPostById = postModel.defineAccessor\u003cPost, number\u003e({\n  fetchData: async arg =\u003e {\n    const data = await getPostApi({ id: arg });\n    return data;\n  },\n  syncState: (draft, payload) =\u003e {\n    postAdapter.upsertOne(draft, payload.data);\n  },\n});\n\nexport function usePost(id: number) {\n  const { data, error, isFetching, accessor } = useAccessor(\n    getPostById(id),\n    state =\u003e {\n      return postAdapter.tryReadOne(state, id);\n    },\n    {\n      revalidateOnFocus: true,\n    }\n  );\n\n  return { data, error, isFetching, revalidate: () =\u003e accessor.revalidate() };\n}\n\nexport const getPostList = postModel.defineInfiniteAccessor\u003cPost[], string\u003e({\n  fetchData: async filter =\u003e {\n    return getPostListApi({ filter });\n  },\n  syncState: (draft, payload) =\u003e {\n    postAdapter.appendPagination(draft, filter, payload.data);\n  },\n});\n\nexport function usePostList(filter: string) {\n  return useAccessor(getPostList(filter), postAdapter.tryReadPaginationFactory(filter));\n}\n```\n\n### Other Examples\n\n- [Simple Forum](https://daxus-simple-forum.vercel.app/)\n\n## Tutorial\n\nIn this tutorial, we will build a simple forum app that contains posts and users' data using Daxus.\n\nWe'll create a model for posts. In Daxus, a model represents a data type from the backend, and it's essential to create separate models for different data types to avoid mixing different data.\n\nBefore creating a model, we need to understand the concept of an \"adapter.\" An adapter is a data access object that provides several operation functions and initial state for the custom data structure defined in the adapter. Daxus currently offers a pagination adapter to handle pagination data.\n\nLet's take a look at how Daxus defines the data structure for pagination data:\n\n```ts\nexport type Id = string | number;\n\nexport interface PaginationMeta {\n  ids: Id[];\n  noMore: boolean;\n}\n\nexport interface PaginationState\u003cData\u003e {\n  entityRecord: Record\u003cstring, Data\u003e;\n  paginationMetaRecord: Record\u003cstring, PaginationMeta\u003e;\n}\n```\n\nThe `entityRecord` stores the data instances, with the keys being the IDs of the instances. The `PaginationMeta` stores `ids` to reference the data in the `entityRecord`. We will discuss the reasons behind this design later.\n\nNow, let's create our post model:\n\n```ts\n// in postModel.ts\nimport { createPaginationAdapter, createModel } from 'daxus';\n\nexport const postAdapter = createPaginationAdapter\u003cPost\u003e();\n\nexport const postModel = createModel({\n  initialState: postAdapter.getInitialState(),\n});\n```\n\nTo create a model, we need to provide the initial state.\n\nNext, we can define an accessor in our model. An accessor is used to fetch remote data and synchronize it with our model. Let's see how to define an accessor:\n\n```ts\n// in postAccessor.ts\nimport { postModel, postAdapter } from './postModel';\n\nexport const getPost = postModel.defineAccessor({\n  async fetchData(id: number) {\n    return getPostApi({ id });\n  },\n  syncState(draft, { data }) {\n    postAdapter.upsertOne(draft, data);\n  },\n});\n```\n\nThe `fetchData` method fetches the remote data, and the `syncState` method synchronizes the fetched data with our model. In this case, we use `postAdapter.upsertOne` to update the post in our model, creating one if it doesn't exist.\n\nTo use the accessor in our React app, we can utilize the `useAccessor` hook:\n\n```ts\nimport { useAccessor } from 'daxus';\nimport { getPost } from 'postAccessor';\nimport { postAdapter } from 'postModel';\n\nexport function usePost(id: number) {\n  return useAccessor(getPost(id), state =\u003e postAdapter.tryReadOne(state, id));\n  // { data, isFetching, isLoading, error, accessor }\n}\n```\n\nThe first argument in `useAccessor` is the accessor, which we obtained from the accessor creator defined in _postAccessor.ts_. The second argument is a function that affects the return value of the `data` field. In this example, we use `postAdapter.tryReadOne` to get the post with the specified `id`, and if the post doesn't exist yet, it will return `undefined`.\n\n\u003e We refer to the second argument as `getSnapshot` in Daxus since it obtains a snapshot from the model.\n\n`useAccessor` will only rerender if the return value of `getSnapshot` changes. For example, the following component will not rerender if the `likeCount` of the corresponding post changes:\n\n```tsx\nexport function PostTitle({ id }: { id: number }) {\n  const { data: title } = useAccessor(\n    getPost(id),\n    state =\u003e postAdapter.tryReadOne(state, id)?.title\n  );\n\n  return \u003cdiv\u003e{title}\u003c/div\u003e;\n}\n```\n\nThe `PostTitle` component will only rerender if the post's title changes.\n\nWhen using `useAccessor`, we can place it at any level in our component without worrying about too many requests, as the accessor internally helps deduplicate the requests.\n\n### Pagination Data\n\nNow, let's delve into the data structure of pagination and define how to fetch the post list:\n\n```ts\nexport interface ListPostOptions {\n  forumId?: string;\n  filter: 'popular' | 'latest' | 'recommended';\n}\n\nexport const getPostPaginationKey = ({ forumId = 'all', filter }: ListPostOptions) =\u003e {\n  return `forumId=${forumId}\u0026filter=${filter}`;\n};\n\nexport const listPost = postModel.defineInfiniteAccessor({\n  async fetchData(options: ListPostOptions, { pageIndex }) {\n    return listPostApi({ ...options, page: pageIndex });\n  },\n  syncState(draft, { arg, data, pageIndex }) {\n    const key = getPostPaginationKey(arg);\n    if (pageIndex === 0) {\n      postAdapter.replacePagination(draft, key, data);\n    } else {\n      postAdapter.appendPagination(draft, key, data);\n    }\n  },\n});\n```\n\nIn this example, we use `forumId` and `filter` to generate a pagination key. Then, we synchronize the state based on the `pageIndex`. If the `pageIndex` is equal to zero, we replace the entire pagination. Otherwise, we append the fetched data to the current pagination.\n\n\u003e The `defineInfiniteAccessor` is almost the same as `defineAccessor`, but it provides `pageIndex` and `previousData` in the `fetchData` and `syncState` methods, allowing us to determine how to fetch the data and how to update the state using them.\n\nNext, let's define a post list component:\n\n```tsx\nexport function PostList({ options }: { options: ListPostOptions }) {\n  const { data, accessor } = useAccessor(listPost(options), state =\u003e\n    postAdapter.tryReadPagination(state, getPostPaginationKey(options))\n  );\n\n  return (\n    \u003cdiv\u003e\n      {data?.items.map(post =\u003e {\n        return \u003cPostEntry key={post.id} post={post} /\u003e;\n      })}\n      \u003cbutton onClick={() =\u003e accessor.fetchNext()}\u003efetch next page\u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n`PostEntry` is a component that contains information about the post, such as the title and excerpt. Clicking on a `PostEntry` will redirect users to the post detail page, which displays more information about the post, including its full content.\n\nFurthermore, users can update the like count by clicking the like button on the post detail page. Let's see how we achieve this in the `PostDetail` component:\n\n```tsx\nexport function PostDetail({ id }: { id: number }) {\n  const { data } = usePost(id);\n\n  if (!data) return \u003cLoading /\u003e;\n\n  const handleLikeButtonClick = async () =\u003e {\n    postModel.mutate(draft =\u003e {\n      postAdapter.readOne(draft, id).likeCount += 1;\n    });\n    try {\n      await incrementPostLikeCountApi(id);\n    } catch {\n      postModel.mutate(draft =\u003e {\n        postAdapter.readOne(draft, id).likeCount -= 1;\n      });\n    }\n  };\n\n  return (\n    \u003cdiv\u003e\n      \u003cdiv\u003e{data.title}\u003c/div\u003e\n      \u003cdiv\u003e{data.content}\u003c/div\u003e\n      \u003cspan\u003eLike count: {data.likeCount}\u003c/span\u003e\n      \u003cbutton onClick={handleLikeButtonClick}\u003eLike!\u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\nIn the `handleLikeButtonClick` function, we first mutate the `postModel` by invoking `postModel.mutate`. This method allows us to directly mutate the model's state. We increment the `likeCount` before calling the API to perform optimistic updating. Then we call `incrementPostLikeCountApi` to update the backend with the result. If it fails, we decrement the `likeCount` to rollback to the original status.\n\nAfter users click the like button, they may want to return to the post list page to view other posts. To show the latest result, we typically revalidate the post list. That is, we refetch the post list to the page index we have fetched. However, there are two obvious problems:\n\n1. Fetching the entire list just because users liked a post seems wasteful.\n2. Users may experience a delay while the post list is being refetched. If a user's network is slow, it may take seconds to see the latest result.\n\nFor the first problem, if the request is not expensive, we can just ignore it. But for the second problem, it may cause a terrible user experience. We need to fix it.\n\nTo solve the second problem, we can use optimistic updating. However, if we just store the API result for different endpoints, it may cause inconsistency. For example, suppose we have 20 post lists, and all of them contain a post with ID 100. When we update this post, we expect all lists to see the updated result. If we just store the API result for different endpoints, we would need to update every list manually, which is obviously impossible since we may have so many post lists.\n\nThis is where the pagination data structure used by Daxus comes to the rescue. Since the pagination data is generated by looking up the `entityRecord`, each data instance references the same entity. Therefore, when we update an entity, all paginations containing this entity will reflect the updated result.\n\n\u003e Currently, Daxus only supports pagination data structure, but you can also build your own data structure to meet your requirements, as Daxus allows for any customized data structure.\n\n### Auto Model\n\nWhile Daxus allows us to customize the data structure, in many cases, we may not need this level of customization. Often, we simply want to store the fetched data directly without the need for a custom data structure. For instance, user data may not require a custom data structure, and storing the API result directly would be sufficient.\n\nTo handle data that doesn't need a custom data structure, Daxus provides a feature called \"Auto Model.\" The auto model is similar to the original model we've seen, but it requires less code to set up. Let's use user data as an example:\n\n```ts\nexport const userModel = createAutoModel();\n\nexport const getUser = userModel.defineAccessor({\n  async fetchData(userId: string) {\n    return getUserApi(userId);\n  },\n});\n\nexport function useUser(id: string) {\n  return useAccessor(getUser(id));\n}\n```\n\nTo create an auto model, we use the `createAutoModel` function, which doesn't require us to provide an initial state. Then, we can use `userModel.defineAccessor` to define the accessor. In the auto model's `defineAccessor`, we don't need to specify `syncState` because it handles it internally for us. Moreover, when using `useAccessor` with an auto model, we don't need to provide the `getSnapshot` function. It will directly return the fetched result that is returned in `fetchData`.\n\nFor more information about the auto model, you can refer to [this page](./docs/auto-model.md).\n\n### Invalidation\n\nWhen users click the like button, we expect the corresponding post's `likeCount` to increase by at least one. However, it may not only increment by one because other users might also like the same post concurrently. Therefore, it's a good idea to refetch the data from the backend at this point. To achieve this, we can leverage the accessor we've defined:\n\n```ts\ngetPost(postId).invalidate();\n```\n\nInvoking `invalidate` will trigger a refetch of the data if the accessor is currently being used by at least one `useAccessor` hook. If there are no active `useAccessor` hooks for the accessor, calling `invalidate` will mark the accessor as \"stale.\" When we use a stale accessor in `useAccessor` and set `revalidateIfStale` to `true`, it will automatically trigger a refetch:\n\n```ts\nuseAccessor(getPost(id), state =\u003e postAdapter.tryReadOne(state, id), {\n  revalidateIfStale: true,\n});\n```\n\nFurthermore, if we want to invalidate all post entities, we can directly call `invalidate` on the `getPost` accessor creator:\n\n```ts\ngetPost.invalidate(); // All accessors generated by this creator will be marked as stale\n```\n\nWe can also invalidate all accessors related to the post model:\n\n```ts\npostModel.invalidate(); // All accessors generated by this model will be marked as stale\n```\n\n## Documents\n\n- [The Difference With React Query](./docs/the-difference-with-RQ.md)\n- [Accessor](./docs/accessor.md)\n- API\n  - [`useAccessor`](./docs/API/useAccessor.md)\n  - [`useSuspenseAccessor`](./docs/API/useSuspenseAccessor.md)\n  - [`useHydrate`](./docs/API/useHydrate.md)\n  - [`useModel`](./docs/API/useModel.md)\n  - [`ServerStateKeyProvider`](./docs/API/ServerStateKeyProvider.md)\n  - [`AccessorOptionsProvider`](./docs/API/AccessorOptionsProvider.md)\n  - [`createModel`](./docs/API/createModel.md)\n- [Pagination](./docs/pagination.md)\n- [Conditional Fetching](./docs/conditional-fetching.md)\n- [SSR](./docs/ssr.md)\n- [Auto Model](./docs/auto-model.md)\n\n## Development Motivation\n\nIn my company, we use Redux with async thunk to manage server state. While Redux brings many benefits with centralized state management, it also comes with some drawbacks. For example, combining all reducers into a single store leads to excessively large initial JavaScript files. Additionally, even with Redux Toolkit, we still need to write a lot of repetitive code. As a result, senior engineers in the company have been considering replacing Redux, but so far, we haven't found a suitable package.\n\n### Why not use React Query?\n\nActually, we have tried incorporating both React Query and SWR into our internal console-type websites, and colleagues find React Query to be more user-friendly than SWR. Although React Query performs well in console-type products, most colleagues believe it is not quite suitable for our main product website.\n\nOur product is a user forum that receives a large number of user visits every day. Here's an example that colleagues think React Query is not suitable for our product: when a user creates a new comment, we want the corresponding post's `totalCommentCount` to increase by one. From the perspective of React Query, we should execute the following code after creating a comment:\n\n```typescript\nqueryClient.invalidateQueries({ queryKey: ['posts', 'get', postId] });\n```\n\nThis way, React Query will automatically request the new post in the background and update the corresponding post. However, considering that our post response is quite large, fetching the entire post just for updating `totalCommentCount` seems wasteful. You might think we can do it this way instead:\n\n```typescript\nqueryClient.setQueryData(['posts', 'get', postId], oldPost =\u003e {\n  const totalCommentCount = oldPost.totalCommentCount + 1;\n  return { ...oldPost, totalCommentCount };\n});\n```\n\nBut there's a problem with this approach. When the user goes back to the post list, the totalCommentCount on the list won't update because the queryKey is different. This may appear odd to observant users. Of course, we can add more code like this:\n\n```typescript\nqueryClient.setQueryData(['posts', 'list'], oldPosts =\u003e {\n  const oldPost = oldPosts.find(post =\u003e post.id === postId);\n  if (!post) return oldPosts;\n  const totalCommentCount = oldPost.totalCommentCount + 1;\n  const newPost = { ...oldPost, totalCommentCount };\n  const oldPostIndex = oldPosts.indexOf(oldPost);\n  const newPosts = [...oldPosts];\n  newPosts.splice(oldPostIndex, 1, newPost);\n  return newPosts;\n});\n```\n\nThis way, we take into account the scenario of updating the list. But is it really that simple? Our list can have various forms, such as \"popular,\" \"latest,\" and different forums with their own lists. The queryKey might look like this:\n\n```typescript\nconst allPopular = ['posts', 'list', 'popular', 'all'];\nconst allLatest = ['posts', 'list', 'latest', 'all'];\nconst forumPopular = ['posts', 'list', 'popular', forumId];\nconst forumLatest = ['posts', 'list', 'latest', forumId];\n```\n\nIf we also consider all these scenarios, it might bring us even more mental burden than using Redux, not to mention some API responses have this format:\n\n```json\n{\n  \"items\": [],\n  \"nextKey\": \"123\"\n}\n```\n\nIf we have to mutate the data using the methods mentioned above, it would be a disaster. Moreover, [it goes against the practical way React Query recommends us to use](https://tkdodo.eu/blog/practical-react-query#dont-use-the-querycache-as-a-local-state-manager). While React Query fits well with console-type websites, unfortunately, it seems less suitable for our main website.\n\n\u003e You may think that we can use `queryClient.setQueriesData` to set all lists, but it will make things more complicated. Moreover, the maintainer of RQ doesn't like use this too. (See [here](https://tkdodo.eu/blog/using-web-sockets-with-react-query#partial-data-updates))\n\nSo, what makes React Query unsuitable for our main website? I believe it's the level of control over the data. React Query focuses on managing server state for us, which means we don't have as much control over the data compared to using Redux. When using Redux, updating a post would automatically update the corresponding post in the list. However, when using Redux, it's not as straightforward as using `useQuery` to retrieve the data. We need to write a lot of actions and reducers, and if we want to add features like deduplication and revalidation, the amount of code to write increases even more. Clearly, Redux is not the optimal choice.\n\nSince we haven't found a suitable package for our use case, why not develop our own? This brings up the issue of maintainability. If we create a tool that only we use, then the responsibility of maintaining it falls solely on us. Lack of community support is a significant concern for senior colleagues.\n\nAs a junior developer, I have always been interested in state management problems. Therefore, I want to try developing my own tool that meets the company's needs as my side project. Of course, I also hope this tool can help other developers who are struggling with managing server state.\n\n### Goals to achieve\n\nFirst and foremost, it is essential to empower users to have full control over their data. Unlike React Query, where server state management is handled for us, all data writes will be user-defined. Although this may require users to write more code, I believe it is a necessary trade-off, and compared to Redux, the amount of code to write is relatively less.\n\nAnother crucial point is to provide a concise and user-friendly hook, similar to `useQuery`, that allows developers to call it from any component without worrying about duplicate requests. Additionally, features like polling and revalidation are also important.\n\nIf you have any ideas or suggestions regarding this project, please feel free to share them with me. Thank you.\n\n## Design Philosophy\n\nUsing Redux has its advantages, especially when it comes to customizing data structures. In our company, pagination is one of the most frequently used data structures for various entities like posts, comments, and forums. To simplify the process of creating paginations, our senior engineer developed a `createPaginationAdapter` function.\n\nThe state type returned by `createPaginationAdapter` is defined as follows:\n\n```typescript\nexport interface Pagination {\n  noMore: boolean;\n  index: EntityId[];\n  loading: boolean;\n  fetched: boolean;\n  error?: any;\n}\n\nexport interface PaginationState\u003cT\u003e {\n  data: Record\u003cEntityId, T | undefined\u003e;\n  paginations: Record\u003cstring, Pagination | undefined\u003e;\n}\n```\n\n`PaginationState` consists of two properties: `data` and `paginations`. `data` stores all the entity data, while `paginations` keeps track of the pagination states and the associated IDs.\n\nTo illustrate, let's consider the example of managing posts. When retrieving the list of posts without any filters, the API endpoint would look like `/api/posts?filter=all`, and the corresponding pagination key would be `filter=all`. Suppose the API returns the first page of posts with IDs 1 to 5. In this case, `paginations[\"filter=all\"]` would contain the following:\n\n```javascript\n{\n    noMore: false,\n    index: [1, 2, 3, 4, 5],\n    loading: false,\n    fetched: true\n}\n```\n\nThe `data` object would store the actual post data:\n\n```javascript\n{\n    1: {\n        // post data\n    },\n    // and so on\n}\n```\n\nTo access the pagination for `filter=all`, we can use the object obtained from `createPaginationAdapter`:\n\n```javascript\nconst postAdapter = createPaginationAdapter();\n\nfunction usePost() {\n  const postPagination = useSelector(state =\u003e\n    postAdapter.selectByPagination(state.post, 'filter=all')\n  );\n  return postPagination;\n}\n```\n\nAt this point, you might think, \"Isn't this similar to RTK's [`createEntityAdapter`](https://redux-toolkit.js.org/api/createEntityAdapter)?\" Indeed, there are similarities between the two, but `createPaginationAdapter` is an enhanced version specifically designed for pagination data. Now, let's delve into its most significant advantage.\n\nDo you remember [when React Query fails to meet our needs](#why-not-use-swr-or-react-query)? Yes, it's when we have multiple paginations that might include the same post. React Query cannot handle this scenario effectively, but the pagination adapter perfectly solves this problem. By centralizing all the data in `data` and using `paginations` to collect the associated IDs, any updates to a specific post will automatically reflect in all the paginations containing that post. There won't be any inconsistencies.\n\nSo, what is the design philosophy behind Daxus?\n\nThe answer lies in **customized data structures**.\n\nEvery application has unique requirements for data structures. In our company, we designed `PaginationState` to fulfill our needs. Daxus's design philosophy empowers developers to define and use data structures tailored to their specific requirements. You only need to invest effort in creating suitable adapters and instructing Daxus on how to fetch data and synchronize it with your model. Daxus takes care of the rest, including deduplication, revalidation, and more.\n\nHowever, creating an adapter does require some code, so Daxus also strives to provide pre-built adapters that cater to most use cases. If you have any new data structures in mind that Daxus doesn't support yet, we welcome your suggestions, and we'll make an effort to implement them.\n\n\u003e Currently, Daxus only offers `createPaginationAdapter`, and I haven't thought of other forms of data structures. If you have any ideas, please let me know!\n\n\u003c!-- images --\u003e\n\n[npm-image]: https://badge.fury.io/js/daxus.svg\n[license-image]: https://img.shields.io/github/license/jason89521/daxus?style=flat-square\n[pr-welcoming-image]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square\n[codecov-image]: https://codecov.io/gh/jason89521/daxus/branch/main/graph/badge.svg\n\n\u003c!-- link --\u003e\n\n[npm-url]: https://www.npmjs.com/package/daxus\n[license-url]: https://github.com/jason89521/daxus\n[pr-welcoming-url]: https://github.com/jason89521/daxus/pull/new\n[codecov-url]: https://codecov.io/github/jason89521/daxus?branch=main\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjason89521%2Fdaxus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjason89521%2Fdaxus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjason89521%2Fdaxus/lists"}