{"id":17855611,"url":"https://github.com/nebrius/react-strapped","last_synced_at":"2026-02-02T02:31:43.351Z","repository":{"id":199324643,"uuid":"702581980","full_name":"nebrius/react-strapped","owner":"nebrius","description":"A React library for managing bootstrap data in multi-page applications","archived":false,"fork":false,"pushed_at":"2023-11-05T20:03:18.000Z","size":743,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-20T22:37:03.910Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/nebrius.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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}},"created_at":"2023-10-09T15:30:40.000Z","updated_at":"2024-03-05T17:35:01.000Z","dependencies_parsed_at":"2023-10-11T20:49:00.534Z","dependency_job_id":"87f7cec7-7afe-4a82-bd61-a548146397e0","html_url":"https://github.com/nebrius/react-strapped","commit_stats":null,"previous_names":["nebrius/react-strapped"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/nebrius/react-strapped","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nebrius%2Freact-strapped","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nebrius%2Freact-strapped/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nebrius%2Freact-strapped/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nebrius%2Freact-strapped/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nebrius","download_url":"https://codeload.github.com/nebrius/react-strapped/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nebrius%2Freact-strapped/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29001654,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-02T01:32:03.847Z","status":"online","status_checked_at":"2026-02-02T02:00:07.448Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2024-10-28T02:23:48.216Z","updated_at":"2026-02-02T02:31:43.326Z","avatar_url":"https://github.com/nebrius.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# React Strapped\n\n\u003cp align=\"center\"\u003e\n  \u003cimg alt=\"version\" src=\"https://img.shields.io/npm/v/react-strapped\" /\u003e\n  \u003ca href=\"https://bundlephobia.com/package/react-strapped\"\u003e\n    \u003cimg alt=\"bundle size\" src=\"https://img.shields.io/bundlephobia/min/react-strapped\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/nebrius/react-strapped/actions/workflows/tests.yml\"\u003e\n    \u003cimg alt=\"build status\" src=\"https://github.com/nebrius/react-strapped/actions/workflows/tests.yml/badge.svg\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\nReact Strapped makes it straightforward to initialize React with runtime bootstrap data in client-side multi-page applications. React Strapped is intentionally small with no runtime dependencies.\n\n- [Motivation](#motivation)\n- [Installation](#installation)\n- [Getting Started](#getting-started)\n  - [Simple example](#simple-example)\n  - [Multi-page Apps](#multi-page-apps)\n  - [Updating strap data after first render](#updating-strap-data-after-first-render)\n- [Frequently Asked Questions](#frequently-asked-questions)\n  - [Is it possible to reset bootstrap data?](#is-it-possible-to-reset-bootstrap-data)\n  - [Is React Strapped server side rendering friendly?](#is-react-strapped-server-side-rendering-friendly)\n  - [Is React Strapped React Server Components friendly?](#is-react-strapped-react-server-components-friendly)\n- [Linking to other state management libraries](#linking-to-other-state-management-libraries)\n  - [Jotai](#jotai)\n  - [Recoil](#recoil)\n  - [Others](#others)\n- [API Specification](#api-specification)\n  - [`createStrap()`](#createstrap)\n  - [`\u003cstrapInstance.Provider bootstrapData={}\u003e...\u003c/BootstrapRoot\u003e`](#strapinstanceprovider-bootstrapdatabootstraproot)\n  - [`strapInstance.createUseStrappedValue(initializer)`](#strapinstancecreateusestrappedvalueinitializer)\n  - [`strapInstance.createUseStrappedState(initializer)`](#strapinstancecreateusestrappedstateinitializer)\n- [License](#license)\n\n## Motivation\n\nReact has a number of popular state management libraries, such as [Redux](https://react-redux.js.org/), [Zustand](https://github.com/pmndrs/zustand), [Jotai](https://jotai.org/), and [Recoil](https://recoiljs.org/). While each of these state management libraries brings their own opinions to state management, none of them have opinions on how to load bootstrap data not available until first render. This creates friction for developing modern applications.\n\nPopular frameworks such as [Next.js](https://nextjs.org/) have given rise to what I call client-side multi-page applications. These applications blur the line between traditional multi-page applications (MPAs) and single-page applications (SPAs). Specifically, they inherit the routing and separation of concerns of traditional MPAs and combine it with client-side routing of SPAs.\n\nWhile I'm a big fan of client-side MPAs, I've always struggled with bootstrap data that's generated during server side rendering (SSR). Bootstrap data in client-side MPAs typically have the following characteristics:\n- Some data is always available on all pages.\n    - Examples include: data about the current user, feature/experimentation flags, etc.\n    - This data is always available, and can be consumed \"no questions asked.\"\n    - In Next.js, this data is typically populated via a centralized helper function that is called from [`getServerSideProps`](https://nextjs.org/docs/pages/api-reference/functions/get-server-side-props).\n- Other data is only available on certain pages.\n    - Examples include settings information that's only available on a settings page, but not on the home page.\n    - This data is _not_ available on all pages, and so care must be take to only access this data on the settings page.\n    - In Next.js, this data is typically populated via one-off code in a specific page(s)' [`getServerSideProps`](https://nextjs.org/docs/pages/api-reference/functions/get-server-side-props) implementation.\n- Bootstrap data is not available until the first render of the application.\n    - In Next.js, bootstrap data generated in [`getServerSideProps`](https://nextjs.org/docs/pages/api-reference/functions/get-server-side-props) is passed as component properties to the top level component.\n    - The implication of this pattern is that any state management declared at module scope, such as `atom()` constructors in Jotai and Recoil, do not have access to this data at time of construction.\n    - This limitation means we can't use these libraries preferrerd state initialization techniques.\n- Data during SSR needs to be scoped to a React context and not be available globally.\n    - A Next.js server is rendering multiple requests from multiple users more or less at once, meaning global data is not a pragmatic option since it wouldn't be \"scoped\" to a specific user.\n    - While it's possible to use global data safely in Next.js applications, it's _very_ tricky to completely prevent all data \"leaks\" at all times. As such, I think it's best to avoid as a whole.\n     - This means we can't do tricks for Jotai/Recoil like creating a global promise we attach to each atom's initializer, and then resolving it once we get the bootstrap data.\n\nTaking these characteristics into account when considering state management libraries, we end up with these requirements for state management:\n1. Initialize the store/atoms/etc. synchronously during the first render\n2. Allow global data to be used anywhere, but prevent page specific data from being used outside of that page\n3. Only load state management code on a page if they are used on that page\n\nRedux/Zustand handle requirement 1. well. Jotai/Recoil handle requirements 1. and 3. well, but only if we're not trying to solve requirement 2. (see https://github.com/nebrius/recoil-bootstrap#motivation for a more in-depth explanation why). None of these four handle requirement 2. well.\n\nReact Strapped exists to handle all 3 requirements well. That said, React Strapped is _not_ a replacement for the four state management libraries mentioned, and is indeed intended to be used in conjunction with them. See [Linking to other state management libraries](#linking-to-other-state-management-libraries) for more information.\n\n_Aside:_ This project grew out of [recoil-bootstrap](https://github.com/nebrius/recoil-bootstrap), a previous library I created to meet these requirements with Recoil. I realized with my previous project that 1) these requirements are not specific to Recoil and 2) Meta appears to have stopped investing in Recoil all together sadly.\n\n## Installation\n\nInstall React Strapped from npm with:\n\n```\nnpm install react-strapped\n```\n\n## Getting Started\n\nReact Strapped works by creating a React context to hold bootstrap data, and special hooks for accessing this data. We call an instance of provider+hooks associated with a piece of bootstrap data a \"strap.\" Unlike other state management libraries, these providers+hooks are intentionally designed so that more than one provider can be used at a time without impacting other providers. We'll see what this means and why it's important in the next section.\n\n### Simple example\n\nThis example shows a minimal example using React Strapped. It's written in TypeScript to a) demonstrate how TypeScript types flows through the library and b) to give a sense of what data is expected where. You can absolutely use this library without using TypeScript though.\n\nFirst, let's create our provider and hooks in a file called `state.ts`:\n\n```tsx\nimport { createStrappedProvider } from 'react-strapped';\n\nexport interface MyBootstrapData {\n  currentUser: {\n    name: string;\n    age: number;\n  };\n}\n\n// First, we create the strap, which includes the context provider and some\n// helper functions for creating hooks\nconst myStrap = createStrap\u003cMyBootstrapData\u003e();\n\n// Next, export the provider to make data available to components\nexport const MyStrapProvider = myStrap.Provider;\n\n// Finally create a hook for accessing the current user included in the\n// bootstrap data. The callback is called once on first render to initialize the\n// strap value returned by the hook.\nexport const useCurrentUser = myStrap.createUseStrappedValue(({currentUser}) =\u003e currentUser);\n```\n\nNow let's create some UI in a Next.js page component:\n\n```tsx\nimport type { MyBootstrapData } from './state';\nimport { MyStrapProvider, useCurrentUser } from './state';\n\ninterface PageProps {\n  bootstrapData: MyBootstrapData;\n}\n\n// If you're not familiar with Next.js, this function runs on a server and is\n// responsible for fetching bootstrap data. The value of the `props` property is\n// passed as props to the default export React component in this file.\nexport function getServerSideProps() {\n  const props: PageProps = {\n    bootstrapData: {\n      currentUser: {\n        name: 'Philip J Fry',\n        age: 1_026,\n      },\n    },\n  };\n  return { props };\n}\n\n// This default export is the root component in a Next.js page. The props\n// passed to this component come from the server via `getServerSideProps`\nexport default function MyApp({ bootstrapData }: PageProps) {\n  return (\n    // We include our strap provider and give it the bootstrap data. This\n    // initializes data and make it immediately available for use via strap\n    // hooks, e.g. `useCurrentUser`\n    \u003cMyStrapProvider bootstrapData={bootstrapData}\u003e\n      \u003cMyComponent /\u003e\n    \u003c/MyStrapProvider\u003e\n  );\n}\n\nfunction MyComponent() {\n  // We use the hook created above, which makes sure that we're calling this\n  // hook in a component with \u003cMyStrapProvider\u003e as a parent in the component tree\n  const currentUser = useCurrentUser();\n  return (\n    // Prints \"Hello Philip J Fry\"\n    \u003cdiv\u003eHello {currentUser.name}\u003c/div\u003e\n  );\n}\n```\n\n### Multi-page Apps\n\nReact Strapped is designed specifically for client-side multi-page applications, which React Strapped supports via multiple Provider components. You can have as many providers as you want with any amount of nesting. All hooks associated with providers are available for use, regardless of where that provider sits in relation to other providers.\n\nThis nesting works differently than in Recoil and Jotai. In Jotai, you can only use atom values associated with the closest [`Provider`](https://github.com/pmndrs/jotai/discussions/682). In Recoil, you can either only access the root-most [`RecoilRoot`](https://recoiljs.org/docs/api-reference/core/RecoilRoot/), or the closest `RecoilRoot` if the `override` property is specified. Neither of these allow you to access all values from all providers, regardless of nesting.\n\nWhy does this distinction matter? In multi-page applications, we often have a set of bootstrap data that is common to all pages as well as bootstrap data that is specific to a page. The natural setup to handle these two sets of data is to create one provider for the common bootstrap data that exists on all pages, and then per-page providers that contain just those pages' data. React Strapped supports exactly this provider setup by allowing any number of straps to be nested, and propagating data appropriately between them.\n\nIn code, we set this up like so:\n\n```tsx\nexport default function MyPage({ commonBootstrapData, myPageBootstrapData }) {\n  return (\n    // Add the common provider here, which is used on all pages\n    \u003cMyCommonStrapProvider bootstrapData={commonBootstrapData}\u003e\n      {/* SomeCommonComponents only has access to hooks from MyCommonStrapProvider */}\n      \u003cSomeCommonComponents /\u003e\n      {/* Add the page specific provider here, which is only used on this page */}\n      \u003cMyPageSpecificStrapProvider bootstrapData={myPageBootstrapData}\u003e\n        {/* SomePageSpecificComponents has access to all hooks from both\n            MyCommonStrapProvider _and_ MyPageSpecificStrapProvider */}\n        \u003cSomePageSpecificComponents /\u003e\n      \u003c/MyPageSpecificStrapProvider\u003e\n    \u003c/MyCommonStrapProvider\u003e\n  )\n}\n```\n\nWith this setup, the `SomePageSpecificComponents` component tree has access to all hooks associated with both the common and page specific straps, but the `SomeCommonComponents` component tree _only_ has access to hooks associated with the common strap.\n\nIf bootstrap data exists across two or more pages, but not all pages, you can create a third strap that is shared between these pages that sits between the common strap and the page specific strap.\n\nTo ensure that `SomeCommonComponents` code cannot access the page specific hooks, these hooks provide guardrails against accessing data from the wrong place. If you try and call a hook based on `MyPageSpecificStrapProvider` from `SomeCommonComponents`, then you'll get an error saying you're trying to access it from the wrong place, like so:\n\n\u003cbr /\u003e\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"img/access-error.png\" width=\"480\" alt=\"Image showing a hook access error\" /\u003e\n\u003c/p\u003e\n\nFor an in-depth example of a multi-page Next.js app using React Strapped, see my [jotai-prototyping](https://github.com/nebrius/jotai-prototyping) repository. In addition to showing off React Strapped, this repository also shows how to integrate Jotai and React Strapped together.\n\n### Updating strap data after first render\n\nReact Strapped includes limited support for updating data. Each strapped set includes a helper function for creating a hook with the same signature as React's [`useState` hook](https://react.dev/reference/react/useState). We can modify the first example from above as follows:\n\n```tsx\nconst myStrap = createStrap\u003cMyBootstrapData\u003e();\n\n// Note how we call createUseStrappedState intsead of createUseStrappedValue\nexport const useCurrentUserState = myStrap.createUseStrappedState(({currentUser}) =\u003e currentUser);\n```\n\nAnd then in a UI component we can do:\n\n```tsx\nconst [currentUser, setCurrentUser] = useCurrentUserState();\n```\n\nThis setter is fairly limited however, and does not include the concept of asynchronous updates, reducers, chaining set values from other atoms/state, etc. If you need to make use of these features, I recommend pairing [Jotai](https://jotai.org/) with React Strapped.\n\n## Frequently Asked Questions\n\n### Is it possible to reset bootstrap data?\n\nYes. Add a `key` to the provider that will cause it to unmount and remount, which will reset the internal React context.\n\n### Is React Strapped server side rendering friendly?\n\nYes. Initialization happens synchronously, so all data will be available for the single rendering pass that happens in server side rendering. All data is also contained inside of a React context, and so can't leak to other rendering passes.\n\n### Is React Strapped React Server Components friendly?\n\nYes, ish. Recoil Bootstrap works just fine with React Server Components. Each server component that fetches bootstrap data can be assigned its own Provider to contain that component tree's bootstrap data.\n\nThe catch is that hooks cannot be used inside of React Server Components, meaning that React Strapped is limited to client-only components. React Strapped includes a `use client` directive in its implementation to force code that uses them to also be client-only.\n\n## Linking to other state management libraries\n\n### Jotai\n\nConnecting a Jotai atom to React Strapped can be accomplished with the [`useHydrateAtoms` utility](https://jotai.org/docs/utilities/ssr).\n\n```tsx\nfunction RootishComponent() {\n  const myFirstStrapValue = useFirstStrapValue(); // Hook returned from a createUseStrappedValue() call\n  const mySecondStrapValue = useSecondStrapValue(); // Hook returned from another createUseStrappedValue() call\n  useHydrateAtoms([\n    [myFirstAtom, myFirstStrapValue],\n    [mSecondAtom, mySecondStrapValue]\n  ]);\n}\n```\n\nThen you can use these as normal atoms.\n\nNote: when connecting React Strapped and Jotai, you'll need to be careful where you put the Jotai Provider, since Jotai [does not supported nested providers](https://github.com/pmndrs/jotai/discussions/682) the same way React Strapped does. I recommend putting the Jotai Provider outside of the top-most React Strapped provider, just to be safe.\n\n### Recoil\n\nUnfortunately I was not able to create a way for this mechanism to work with Recoil, due to [this bug which has gone unanswered](https://github.com/facebookexperimental/Recoil/issues/2256).\n\n### Others\n\nI haven't experimented with Redux/Zustand and React Strapped, but I suspect it will be tricky to do. Redux and Zustand centralize store creation, meaning you can't do distributed initialization of slices/reducers across different layers of the app. There may be some clever tricks to setting up a slices/reducers so they get initialized properly further down the React component tree, but I haven't figured one out yet.\n\n## API Specification\n\n### `createStrap()`\n\nCreates a strap. Data associated with this strap is typed as `BootstrapData`\n\n```ts\nexport function createStrap\u003cBootstrapData\u003e(): {\n  createUseStrappedState,\n  createUseStrappedValue,\n  Provider,\n}\n```\n\n_**Props:**_\n\n_none_\n\n_**Returns:**_\n\nAn object with the three properties:\n\n- `Provider`\n- `createUseStrappedValue`\n- `createUseStrappedState`\n\nSee below for more information on these properties.\n\n### `\u003cstrapInstance.Provider bootstrapData={}\u003e...\u003c/BootstrapRoot\u003e`\n\nThis component initializes the strap with the supplied bootstrap data.\n\n```ts\nfunction Provider({ children, bootstrapData, }: PropsWithChildren\u003c{\n    bootstrapData: BootstrapData;\n}\u003e): JSX.Element\n```\n_**Props:**_\n\n`bootstrapData`: `BootstrapData`\n\nThe bootstrap data to initialize the strap with.\n\n### `strapInstance.createUseStrappedValue(initializer)`\n\nCreates a hook that returns the value created in the supplied initializer.\n\n```ts\ntype Initializer\u003cStrapValue, BootstrapData\u003e = (\n  bootstrapData: BootstrapData,\n) =\u003e StrapValue;\n\nfunction createUseStrappedValue\u003cStrapValue, BootstrapData\u003e(\n  initializer: Initializer\u003cStrapValue, BootstrapData\u003e\n): () =\u003e StrapValue;\n```\n\n_**Props:**_\n\n`initializer`: `(bootstrapData: BootstrapData) =\u003e StrapValue`\n\nThe intializer is called during the first render of the Provider component associated with with this strap. The first argument passed to the intitializer is the same value passed in for the `bootstrapData` property on the Provider, and the returned value is the value of the hook.\n\n_**Returns:**_\n\nA hook that return the value of the strap, as returned from the initializer.\n\n_**Throws:**_\n\nThe returned hook will throw an exception if it is called from outside a descendant of the Provider component.\n\n### `strapInstance.createUseStrappedState(initializer)`\n\nCreates a hook that returns the value and a setter created in the supplied initializer.\n\n```ts\ntype Initializer\u003cStrapValue, BootstrapData\u003e = (\n  bootstrapData: BootstrapData,\n) =\u003e StrapValue;\n\nfunction createUseStrappedValue\u003cStrapValue\u003e(\n  initializer: Initializer\u003cStrapValue, BootstrapData\u003e\n): () =\u003e [value: StrapValue, setValue: (newValue: StrapValue) =\u003e void];\n```\n\n_**Props:**_\n\n`initializer`: `(bootstrapData: BootstrapData) =\u003e StrapValue`\n\nThe intializer is called during the first render of the Provider component associated with with this strap. The first argument passed to the intitializer is the same value passed in for the `bootstrapData` property on the Provider, and the returned value is the value of the hook.\n\n_**Returns:**_\n\nA hook that return the value of the strap, as returned from the initializer, and a setter to update the value of this strap.\n\n_**Throws:**_\n\nThe returned hook will throw an exception if it is called from outside a descendant of the Provider component.\n\n## License\n\nMIT License\n\nCopyright (c) 2023 Bryan Hughes\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnebrius%2Freact-strapped","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnebrius%2Freact-strapped","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnebrius%2Freact-strapped/lists"}