{"id":13760597,"url":"https://github.com/ricokahler/next-data-hooks","last_synced_at":"2025-05-15T11:06:16.663Z","repository":{"id":36977062,"uuid":"287864265","full_name":"ricokahler/next-data-hooks","owner":"ricokahler","description":"Use `getStaticProps`/`getServerSideProps` as react-hooks","archived":false,"fork":false,"pushed_at":"2025-05-12T01:37:14.000Z","size":3018,"stargazers_count":718,"open_issues_count":9,"forks_count":14,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-05-12T02:37:06.502Z","etag":null,"topics":["nextjs","react-hooks"],"latest_commit_sha":null,"homepage":"","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/ricokahler.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":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-08-16T03:11:18.000Z","updated_at":"2025-05-12T01:37:17.000Z","dependencies_parsed_at":"2023-10-15T06:51:40.083Z","dependency_job_id":"a17c3484-440d-46dd-a6c6-bad722b6ab5f","html_url":"https://github.com/ricokahler/next-data-hooks","commit_stats":{"total_commits":490,"total_committers":6,"mean_commits":81.66666666666667,"dds":"0.44897959183673475","last_synced_commit":"b40001e035db0e933ce519bd95beb280ef597e44"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ricokahler%2Fnext-data-hooks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ricokahler%2Fnext-data-hooks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ricokahler%2Fnext-data-hooks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ricokahler%2Fnext-data-hooks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ricokahler","download_url":"https://codeload.github.com/ricokahler/next-data-hooks/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254328385,"owners_count":22052632,"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":["nextjs","react-hooks"],"created_at":"2024-08-03T13:01:13.741Z","updated_at":"2025-05-15T11:06:16.643Z","avatar_url":"https://github.com/ricokahler.png","language":"TypeScript","funding_links":[],"categories":["TypeScript","Nextjs Plugins"],"sub_categories":[],"readme":"# next-data-hooks · [![codecov](https://codecov.io/gh/ricokahler/next-data-hooks/branch/main/graph/badge.svg)](https://codecov.io/gh/ricokahler/next-data-hooks) [![github status checks](https://badgen.net/github/checks/ricokahler/next-data-hooks/main)](https://github.com/ricokahler/next-data-hooks/actions) [![bundlephobia](https://badgen.net/bundlephobia/minzip/next-data-hooks)](https://bundlephobia.com/result?p=next-data-hooks)\n\n\u003e Use `getStaticProps` and `getServerSideProps` as react hooks\n\n`next-data-hooks` is a small and simple lib that lets you write React hooks for data queries in Next.js by lifting static props into React Context.\n\n```js\nimport { createDataHook } from 'next-data-hooks';\n\nconst useBlogPost = createDataHook('BlogPost', async (context) =\u003e {\n  const { slug } = context.params;\n\n  return; // ... get the blog post\n});\n\nfunction BlogPost() {\n  const { title, content } = useBlogPost();\n\n  return (\n    \u003c\u003e\n      \u003ch1\u003e{title}\u003c/h1\u003e\n      \u003cp\u003e{content}\u003c/p\u003e\n    \u003c/\u003e\n  );\n}\n\nBlogPost.dataHooks = [useBlogPost];\n\nexport default BlogPost;\n```\n\n## Why?\n\n1. Writing one large query per page doesn't organize well. Asynchronous data fetching frameworks like apollo, relay, and react-query already allow you to write the queries closer to the component. Why can't static data queries be written closer to the component too?\n2. Works better with TypeScript — when you import a data hook, you're also importing its return type. When you call the hook inside your component, the types are already there.\n\n## Why not?\n\nThe primary thing this library offers is a pattern _organizing_ `getStaticProps`/`getServerSideProps`.\n\n⚠️ Note: **It does not offer any more capabilities than vanilla Next.js.**\n\nSee this question: [Why aren't the data hooks parameterized?](https://github.com/ricokahler/next-data-hooks/issues/196)\n\n## Example\n\nSee [the example in this repo](https://github.com/ricokahler/next-data-hooks/tree/main/examples/next-data-hooks-example) for some ideas on how to organize your static data calls using this hook.\n\n## Installation\n\n1. Install\n\n```\nnpm i next-data-hooks\n```\n\nor\n\n```\nyarn add next-data-hooks\n```\n\n2. Add the babel plugin\n\nAt the root, add a `.babelrc` file that contains the following:\n\n```json\n{\n  \"presets\": [\"next/babel\"],\n  \"plugins\": [\"next-data-hooks/babel\"]\n}\n```\n\n\u003e ⚠️ Don't forget this step. This enables [**code elimination**](#code-elimination) to eliminate server-side code in client code.\n\n3. Add the provider to `_app.tsx` or `_app.js`\n\n```tsx\nimport { AppProps } from 'next/app';\nimport { NextDataHooksProvider } from 'next-data-hooks';\n\nfunction App({ Component, pageProps }: AppProps) {\n  const { children, ...rest } = pageProps;\n\n  return (\n    \u003cNextDataHooksProvider {...rest}\u003e\n      \u003cComponent {...rest}\u003e{children}\u003c/Component\u003e\n    \u003c/NextDataHooksProvider\u003e\n  );\n}\n```\n\n## Usage\n\n1. Create a data hook. This can be in the same file as the component you're using it in or anywhere else.\n\n```tsx\nimport { createDataHook } from 'next-data-hooks';\n\n// this context is the GetStaticPropsContext from 'next'\n//                                                      👇\nconst useBlogPost = createDataHook('BlogPost', async (context) =\u003e {\n  const slug = context.params?.slug as string;\n\n  // do something async to grab the data your component needs\n  const blogPost = /* ... */;\n\n  return blogPost;\n});\n\nexport default useBlogPost;\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\nTypeScript User?\n\n\u003e Note: For TypeScript users, if you're planning on only using the data hook in the context of `getServerSideProps`, you can import the provided [type guard](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards), `isServerSidePropsContext`, to narrow the type of the incoming context.\n\n\u003c/summary\u003e\n\n```tsx\nimport { createDataHook, isServerSidePropsContext } from 'next-data-hooks';\n\nconst useServerSideData = createDataHook('Data', async (context) =\u003e {\n  if (!isServerSidePropsContext(context)) {\n    throw new Error('This data hook only works in getServerSideProps.');\n  }\n\n  // here, the type of `context` has been narrowed to the server side conext\n  const query = context.req.query;\n});\n\nexport default useServerSideData;\n```\n\n\u003c/details\u003e\n\n2. Use the data hook in a component. Add it to a static prop in an array with other data hooks to compose them downward.\n\n```tsx\nimport ComponentThatUsesDataHooks from '..';\nimport useBlogPost from '..';\nimport useOtherDataHook from '..';\n\nfunction BlogPostComponent() {\n  const { title, content } = useBlogPost();\n  const { other, data } = useOtherDataHook();\n\n  return (\n    \u003carticle\u003e\n      \u003ch1\u003e{title}\u003c/h1\u003e\n      \u003cp\u003e{content}\u003c/p\u003e\n      \u003cp\u003e\n        {other} {data}\n      \u003c/p\u003e\n    \u003c/article\u003e\n  );\n}\n\n// compose together other data hooks\nBlogPostComponent.dataHooks = [\n  ...ComponentThatUsesDataHooks.dataHooks,\n  useOtherDataHooks,\n  useBlogPost,\n];\n\nexport default BlogPostComponent;\n```\n\n3. Pass the data hooks down in `getStaticProps` or `getServerSideProps`.\n\n```tsx\nimport { getDataHooksProps } from 'next-data-hooks';\nimport { GetStaticPaths, GetStaticProps } from 'next';\nimport BlogPostComponent from '..';\n\nexport const getStaticPaths: GetStaticPaths = async (context) =\u003e {\n  // return static paths...\n};\n\n// NOTE: this will also work with `getServerSideProps`\nexport const getStaticProps: GetStaticProps = async (context) =\u003e {\n  const dataHooksProps = await getDataHooksProps({\n    context,\n    // this is an array of all data hooks from the `dataHooks` static prop.\n    //                             👇👇👇\n    dataHooks: BlogPostComponent.dataHooks,\n  });\n\n  return {\n    props: {\n      // spread the props required by next-data-hooks\n      ...dataHooksProps,\n\n      // add additional props to Next.js here\n    },\n  };\n};\n\nexport default BlogPostComponent;\n```\n\n## Useful Patterns\n\n### A separate `routes` directory\n\nNext.js has a very opinionated file-based routing mechanism that doesn't allow you to put a file in the `/pages` folder without it being considered a page.\n\nSimply put, this doesn't allow for much organization.\n\nWith `next-data-hooks`, you can treat the `/pages` folder as a folder of entry points and organize files elsewhere.\n\n```\nmy-project\n# think of the pages folder as entry points to your routes\n├── pages\n│   ├── blog\n│   │   ├── [slug].ts\n│   │   └── index.ts\n│   └── shop\n│       ├── category\n│       │   └── [slug].ts\n│       ├── index.ts\n│       └── product\n│           └── [slug].ts\n|\n# think of each route folder as its own app with it's own components and helpers\n└── routes\n    ├── blog\n    │   ├── components\n    │   │   ├── blog-index.tsx\n    │   │   ├── blog-post-card.tsx\n    │   │   └── blog-post.tsx\n    │   └── helpers\n    │       └── example-blog-helper.ts\n    └── shop\n        ├── components\n        │   ├── category.tsx\n        │   ├── product-description.tsx\n        │   └── product.tsx\n        └── helpers\n            └── example-shop-helper.ts\n```\n\n#### `/routes/blog/components/blog-post.tsx`\n\n```tsx\nimport { createDataHook } from 'next-data-hooks';\n\n// write your data hook in a co-located place\nconst useBlogPostData = createDataHook('BlogPost', async (context) =\u003e {\n  const blogPostData = // get blog post data…\n  return blogPostData;\n});\n\nfunction BlogPost() {\n  // use it in the component\n  const { title, content } = useBlogPostData();\n\n  return (\n    \u003carticle\u003e\n      \u003ch1\u003e{title}\u003c/h1\u003e\n      \u003cp\u003e{content}\u003c/p\u003e\n    \u003c/article\u003e\n  );\n}\n\nBlogPost.dataHooks = [useBlogPostData];\n\nexport default BlogPost;\n```\n\n#### `/pages/blog/[slug].ts`\n\n```ts\nimport { GetStaticProps, GetStaticPaths } from 'next';\nimport { getDataHooksProps } from 'next-data-hooks';\nimport BlogPost from 'routes/blog/components/blog-post';\n\nexport const getStaticPaths: GetStaticPaths = {}; /* ... */\n\nexport const getStaticProps: GetStaticProps = async (context) =\u003e {\n  const dataHooksProps = getDataHooksProps({\n    context,\n    dataHooks: BlogPost.dataHooks,\n  });\n  return { props: dataHooksProps };\n};\n\n// re-export your component. this file is just an entry point\nexport default BlogPost;\n```\n\n\u003e **👋 Note:** the above is just an example of how you can use `next-data-hooks` to organize your project. The main takeaway is that you can re-export page components to change the structure and `next-data-hooks` works well with this pattern.\n\n### Composing data hooks\n\nEach data hook exposes a `getData` method which is simply the function you pass into `createDataHook`.\n\nThis can be used within other data hooks to pull the same data:\n\n```tsx\nimport { createDataHook } from 'next-data-hooks';\n\nconst useHook = createDataHook('DataHook', async (context) =\u003e {\n  return; // ...\n});\n\nexport default useHook;\n```\n\n```tsx\nimport useHook from './';\n\nconst useOtherHook = createDataHook('Other', async (context) =\u003e {\n  const data = await useHook.getData(context);\n\n  // use data to do something…\n});\n```\n\n\u003e **👋 Note:** Be aware that this method re-runs the function.\n\n## Code elimination\n\nFor smaller bundles, Next.js eliminates code that is only intended to run inside `getStaticProps`.\n\n`next-data-hooks` does the same by a babel plugin that prefixes your data hook definition with `typeof window !== 'undefined' ? \u003cstub\u003e : \u003creal data hook\u003e`.\n\nThis works because [Next.js pre-evaluates the expression `typeof window` to `'object'` in browsers.](https://github.com/vercel/next.js/issues/5354#issuecomment-650170660) This will make the above ternary always evaluate to the `\u003cstub\u003e` in the browser. Terser then shakes away the `\u003creal data hook\u003e` expression eliminating it from the browser bundle.\n\nIf you saw the error `Create data hook was run in the browser.` then something may have went wrong with the code elimination. Please open an issue.\n\n\u003e **👋 Note**. There may be differences in Next.js's default code elimination and `next-data-hooks` code elimination. Double check your bundle.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fricokahler%2Fnext-data-hooks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fricokahler%2Fnext-data-hooks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fricokahler%2Fnext-data-hooks/lists"}