{"id":23795009,"url":"https://github.com/walter0b/react-cmp-selector","last_synced_at":"2026-02-03T12:01:03.459Z","repository":{"id":255446022,"uuid":"851210598","full_name":"Walter0b/react-cmp-selector","owner":"Walter0b","description":"An npm package designed to simplify component selection in React applications","archived":false,"fork":false,"pushed_at":"2025-04-11T14:29:37.000Z","size":30,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-21T02:46:07.122Z","etag":null,"topics":["cmp","npm","package","react"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/react-cmp-selector?activeTab=readme","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Walter0b.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2024-09-02T16:28:18.000Z","updated_at":"2025-07-23T14:24:56.000Z","dependencies_parsed_at":"2025-04-11T15:33:17.490Z","dependency_job_id":null,"html_url":"https://github.com/Walter0b/react-cmp-selector","commit_stats":null,"previous_names":["walter0b/react-cmp-selector"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Walter0b/react-cmp-selector","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Walter0b%2Freact-cmp-selector","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Walter0b%2Freact-cmp-selector/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Walter0b%2Freact-cmp-selector/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Walter0b%2Freact-cmp-selector/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Walter0b","download_url":"https://codeload.github.com/Walter0b/react-cmp-selector/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Walter0b%2Freact-cmp-selector/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29045440,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-03T10:09:22.136Z","status":"ssl_error","status_checked_at":"2026-02-03T10:09:16.814Z","response_time":96,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["cmp","npm","package","react"],"created_at":"2025-01-01T19:19:38.950Z","updated_at":"2026-02-03T12:01:02.253Z","avatar_url":"https://github.com/Walter0b.png","language":"TypeScript","readme":"# react-cmp-selector\n\nA powerful and extensible utility for selecting and manipulating React components based on attributes. Ideal for dynamic layouts, slotted rendering, and component injection patterns.\n\n---\n\n## Table of Contents\n\n- [react-cmp-selector](#react-cmp-selector)\n  - [Table of Contents](#table-of-contents)\n  - [Installation](#installation)\n  - [Motivation](#motivation)\n  - [Table of Proposals](#table-of-proposals)\n  - [Usage](#usage)\n    - [1. Basic Slot Matching](#1-basic-slot-matching)\n    - [2. Matching Multiple Components](#2-matching-multiple-components)\n    - [3. Combining Event Handlers](#3-combining-event-handlers)\n    - [4. Declarative API with `\u003cSlot\u003e`](#4-declarative-api-with-slot)\n    - [5. Fallback Slot Content](#5-fallback-slot-content)\n    - [6. Slot Markers](#6-slot-markers)\n    - [7. Slot Validation](#7-slot-validation)\n  - [Use Cases](#use-cases)\n  - [API Reference](#api-reference)\n    - [`getCmpByAttr\u003cP\u003e()`](#getcmpbyattrp)\n    - [`ComponentFinderProps`](#componentfinderprops)\n    - [`Slot`](#slot)\n    - [`SlotUtils`](#slotutils)\n  - [Caveats](#caveats)\n  - [License](#license)\n\n---\n\n## Installation\n\n```bash\nnpm install react-cmp-selector\n```\n\nor using yarn:\n\n```bash\nyarn add react-cmp-selector\n```\n\n---\n\n## Motivation\n\nReact does not support named slots or dynamic selection of children out of the box. This utility solves that by providing:\n\n- A **hook** to search through children by attribute\n- A **component-based API** (`\u003cSlot\u003e`) for declarative slot usage\n- Tools to **inject props**, **merge handlers**, and **validate layout contracts**\n\n---\n\n## Table of Proposals\n\n| Feature                       | Prop / Option              | Type                              | Description                                                   |\n| ----------------------------- | -------------------------- | --------------------------------- | ------------------------------------------------------------- |\n| **Attribute Matching**        | `attribute`                | `string`                          | Attribute name to search for (default: `'data-slot'`)         |\n| **Value Matching**            | `value` / `name`           | `string`                          | The value to match against the selected attribute             |\n| **Prop Merging**              | `props`                    | `Partial\u003cP\u003e`                      | Injected props to merge into the matched component(s)         |\n| **Function Merging Strategy** | `functionPropMerge`        | `'combine' \\| 'override'`         | Defines how function props like `onClick` should be merged    |\n| **Debug Mode**                | `debug`                    | `boolean`                         | Enables logging of matching and merging behavior              |\n| **Match All**                 | `findAll`                  | `boolean`                         | If true, returns all matching components instead of the first |\n| **Hook Interface**            | `getCmpByAttr()`           | —                                 | Programmatic interface to extract and modify children         |\n| **Declarative API**           | `\u003cSlot\u003e`                   | —                                 | React component alternative to the hook                       |\n| **Slot Markers**              | `SlotUtils.createMarker()` | —                                 | Creates a named slot wrapper component                        |\n| **Slot Validation**           | `SlotUtils.validate()`     | —                                 | Dev-only validation for required slot presence                |\n| **Fallback Rendering**        | `fallback`                 | `ReactNode`                       | Rendered if no matching slot is found                         |\n| **onFound Callback**          | `onFound`                  | `(element: ReactElement) =\u003e void` | Runs when a match is found (e.g. for side effects)            |\n\n---\n\n## Usage\n\n### 1. Basic Slot Matching\n\n**Children**\n\n```tsx\nexport function ChildComponents() {\n  return (\n    \u003c\u003e\n      \u003cdiv data-slot=\"header\"\u003eHeader\u003c/div\u003e\n      \u003cdiv data-slot=\"body\"\u003eBody\u003c/div\u003e\n      \u003cdiv data-slot=\"footer\"\u003eFooter\u003c/div\u003e\n    \u003c/\u003e\n  );\n}\n```\n\n**Parent**\n\n```tsx\nconst header = getCmpByAttr({\n  children: \u003cChildComponents /\u003e,\n  value: \"header\",\n  props: { className: \"highlighted\" },\n});\n```\n\n**Output**\n\n```html\n\u003cdiv data-slot=\"header\" class=\"highlighted\"\u003eHeader\u003c/div\u003e\n```\n\n**How It Works**\n\n- `getCmpByAttr()` searches children for `data-slot=\"header\"`.\n- The match is cloned with the `className` prop added.\n\n---\n\n### 2. Matching Multiple Components\n\n**Children**\n\n```tsx\nfunction Buttons() {\n  return (\n    \u003c\u003e\n      \u003cbutton data-role=\"action-button\"\u003eSave\u003c/button\u003e\n      \u003cbutton data-role=\"action-button\"\u003eCancel\u003c/button\u003e\n    \u003c/\u003e\n  );\n}\n```\n\n**Parent**\n\n```tsx\nconst buttons = getCmpByAttr({\n  children: \u003cButtons /\u003e,\n  attribute: \"data-role\",\n  value: \"action-button\",\n  findAll: true,\n  props: { \"data-tracked\": true },\n});\n```\n\n**Output**\n\n```html\n\u003cbutton data-role=\"action-button\" data-tracked=\"true\"\u003eSave\u003c/button\u003e\n\u003cbutton data-role=\"action-button\" data-tracked=\"true\"\u003eCancel\u003c/button\u003e\n```\n\n---\n\n### 3. Combining Event Handlers\n\n**Children**\n\n```tsx\nfunction CTA() {\n  return (\n    \u003cbutton data-slot=\"cta\" onClick={() =\u003e console.log(\"child\")}\u003e\n      Click Me\n    \u003c/button\u003e\n  );\n}\n```\n\n**Parent**\n\n```tsx\nconst cta = getCmpByAttr({\n  children: \u003cCTA /\u003e,\n  value: \"cta\",\n  props: {\n    onClick: () =\u003e console.log(\"parent\"),\n  },\n  functionPropMerge: \"combine\",\n});\n```\n\n**Behavior**\n\nConsole logs:\n\n```\nchild\nparent\n```\n\n---\n\n### 4. Declarative API with `\u003cSlot\u003e`\n\n```tsx\nexport function Layout({ children }: { children: React.ReactNode }) {\n  return (\n    \u003cdiv\u003e\n      \u003cSlot name=\"header\"\u003e{children}\u003c/Slot\u003e\n      \u003cmain\u003e\n        \u003cSlot name=\"content\"\u003e{children}\u003c/Slot\u003e\n      \u003c/main\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n```tsx\nfunction PageContent() {\n  return (\n    \u003c\u003e\n      \u003cdiv data-slot=\"header\"\u003eWelcome\u003c/div\u003e\n      \u003cdiv data-slot=\"content\"\u003eHello, world!\u003c/div\u003e\n    \u003c/\u003e\n  );\n}\n\n\u003cLayout\u003e\n  \u003cPageContent /\u003e\n\u003c/Layout\u003e;\n```\n\n**Output**\n\n```html\n\u003cdiv\u003e\n  \u003cdiv data-slot=\"header\"\u003eWelcome\u003c/div\u003e\n  \u003cmain\u003e\n    \u003cdiv data-slot=\"content\"\u003eHello, world!\u003c/div\u003e\n  \u003c/main\u003e\n\u003c/div\u003e\n```\n\n---\n\n### 5. Fallback Slot Content\n\n```tsx\n\u003cSlot name=\"hero\" fallback={\u003cdiv\u003eDefault Hero\u003c/div\u003e}\u003e\n  {children}\n\u003c/Slot\u003e\n```\n\nIf no `data-slot=\"hero\"` is found, it renders:\n\n```html\n\u003cdiv\u003eDefault Hero\u003c/div\u003e\n```\n\n---\n\n### 6. Slot Markers\n\n**Marker Declaration**\n\n```tsx\nconst HeroSlot = SlotUtils.createMarker(\"hero\");\n\nfunction Page() {\n  return (\n    \u003cHeroSlot\u003e\n      \u003cdiv className=\"hero-banner\"\u003eCustom Hero\u003c/div\u003e\n    \u003c/HeroSlot\u003e\n  );\n}\n```\n\n**Parent**\n\n```tsx\n\u003cSlot name=\"hero\" fallback={\u003cdiv\u003eDefault Hero\u003c/div\u003e}\u003e\n  \u003cPage /\u003e\n\u003c/Slot\u003e\n```\n\n---\n\n### 7. Slot Validation\n\n```tsx\nSlotUtils.validate(children, [\"header\", \"footer\"]);\n```\n\n- Dev-only.\n- Warns if `data-slot=\"header\"` or `footer` is missing in children.\n\n---\n\n## Use Cases\n\n- **Composable Layouts**: Dynamically slot content into shared layouts.\n- **Design Systems**: Enable flexible API layers with predictable slot names.\n- **Multi-brand / White-label UIs**: Inject branding-specific content without hardcoding.\n- **Next.js Layouts**: Use context + slots to bridge `app/layout.tsx` and pages.\n- **Dynamic Prop Injection**: Apply analytics, A/B testing, or class injection to specific slots.\n\n---\n\n## API Reference\n\n### `getCmpByAttr\u003cP\u003e()`\n\n```ts\nfunction getCmpByAttr\u003cP\u003e(\n  options: ComponentFinderProps\u003cP\u003e\n): ReactNode | ReactNode[] | null;\n```\n\n### `ComponentFinderProps`\n\n```ts\ninterface ComponentFinderProps\u003cP = unknown\u003e {\n  children: ReactNode;\n  attribute?: string;\n  value?: string;\n  props?: Partial\u003cP\u003e;\n  debug?: boolean;\n  findAll?: boolean;\n  onFound?: (component: ReactElement) =\u003e void;\n  functionPropMerge?: \"combine\" | \"override\";\n}\n```\n\n### `Slot`\n\n```tsx\n\u003cSlot\n  name=\"footer\"\n  props={{ className: \"sticky\" }}\n  fallback={\u003cDefaultFooter /\u003e}\n\u003e\n  {children}\n\u003c/Slot\u003e\n```\n\n### `SlotUtils`\n\n```ts\nSlotUtils.createMarker(name: string, attribute?: string): Component\nSlotUtils.validate(children: ReactNode, requiredSlots: string[], attribute?: string): void\n```\n\n---\n\n## Caveats\n\n- **Next.js layouts** require a shared context if crossing page boundaries.\n- `getCmpByAttr` only works on elements rendered within the same render cycle.\n- This is **not a DOM query tool** – it’s entirely based on **React element trees**.\n\n---\n\n## License\n\nMIT License\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwalter0b%2Freact-cmp-selector","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwalter0b%2Freact-cmp-selector","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwalter0b%2Freact-cmp-selector/lists"}