{"id":19533951,"url":"https://github.com/rafgraph/polymorphic-as-prop-api","last_synced_at":"2026-04-16T11:01:48.408Z","repository":{"id":57153479,"uuid":"372233731","full_name":"rafgraph/polymorphic-as-prop-api","owner":"rafgraph","description":"Polymorphic as prop api standard proof of concept. Brings composability to the polymorphic as prop.","archived":false,"fork":false,"pushed_at":"2021-06-08T13:53:38.000Z","size":615,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-29T09:34:15.506Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://codesandbox.io/s/github/rafgraph/polymorphic-as-prop-api/tree/main/demo?file=/src/PolymorphicAsArrayDemos.tsx","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/rafgraph.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}},"created_at":"2021-05-30T14:19:35.000Z","updated_at":"2023-06-20T17:00:59.000Z","dependencies_parsed_at":"2022-09-07T08:40:50.173Z","dependency_job_id":null,"html_url":"https://github.com/rafgraph/polymorphic-as-prop-api","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":"rafgraph/rollpkg-example-package","purl":"pkg:github/rafgraph/polymorphic-as-prop-api","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafgraph%2Fpolymorphic-as-prop-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafgraph%2Fpolymorphic-as-prop-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafgraph%2Fpolymorphic-as-prop-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafgraph%2Fpolymorphic-as-prop-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rafgraph","download_url":"https://codeload.github.com/rafgraph/polymorphic-as-prop-api/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafgraph%2Fpolymorphic-as-prop-api/sbom","scorecard":{"id":758585,"data":{"date":"2025-08-11","repo":{"name":"github.com/rafgraph/polymorphic-as-prop-api","commit":"e7d2db64e45d919d9421e64550e6a6d8734b206a"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/6 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"133 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-whgm-jr23-g3j9","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-fwr7-v2mv-hh25","Warn: Project is vulnerable to: GHSA-qwcr-r2fm-qrc7","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-x9w5-v3q2-3rhw","Warn: Project is vulnerable to: GHSA-w8qv-6jwh-64r5","Warn: Project is vulnerable to: GHSA-pxg6-pf52-xh8x","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-phwq-j96m-2c2q","Warn: Project is vulnerable to: GHSA-ghr5-ch3p-vcr6","Warn: Project is vulnerable to: GHSA-434g-2637-qmqr","Warn: Project is vulnerable to: GHSA-49q7-c7j4-3p7m","Warn: Project is vulnerable to: GHSA-977x-g7h5-7qgw","Warn: Project is vulnerable to: GHSA-f7q4-pwc6-w24p","Warn: Project is vulnerable to: GHSA-fc9h-whq2-v747","Warn: Project is vulnerable to: GHSA-vjh7-7g9h-fjfh","Warn: Project is vulnerable to: GHSA-4gmj-3p3h-gm8h","Warn: Project is vulnerable to: GHSA-6h5x-7c5m-7cr7","Warn: Project is vulnerable to: GHSA-rv95-896h-c2vc","Warn: Project is vulnerable to: GHSA-qw6h-vgh9-j6wx","Warn: Project is vulnerable to: GHSA-74fj-2j2h-c42q","Warn: Project is vulnerable to: GHSA-pw2r-vq6v-hr8c","Warn: Project is vulnerable to: GHSA-jchw-25xp-jwwc","Warn: Project is vulnerable to: GHSA-cxjh-pqwp-8mfp","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-8mmm-9v2q-x3f9","Warn: Project is vulnerable to: GHSA-c7qv-q95q-8v27","Warn: Project is vulnerable to: GHSA-33f9-j839-rf8h","Warn: Project is vulnerable to: GHSA-c36v-fmgq-m8hx","Warn: Project is vulnerable to: GHSA-78xj-cgh5-2h22","Warn: Project is vulnerable to: GHSA-2p57-rm9w-gvfp","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-76p3-8jx3-jpfq","Warn: Project is vulnerable to: GHSA-3rfm-jhwj-7488","Warn: Project is vulnerable to: GHSA-hhq3-ff78-jv3g","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-qrpm-p2h7-hrv2","Warn: Project is vulnerable to: GHSA-mwcw-c2x4-8c55","Warn: Project is vulnerable to: GHSA-5rrq-pxf6-6jx5","Warn: Project is vulnerable to: GHSA-8fr3-hfg3-gpgp","Warn: Project is vulnerable to: GHSA-gf8q-jrpm-jvxq","Warn: Project is vulnerable to: GHSA-2r2c-g63r-vccr","Warn: Project is vulnerable to: GHSA-cfm4-qjh2-4765","Warn: Project is vulnerable to: GHSA-x4jg-mjrx-434g","Warn: Project is vulnerable to: GHSA-rp65-9cf3-cjxr","Warn: Project is vulnerable to: GHSA-76c9-3jph-rj3q","Warn: Project is vulnerable to: GHSA-9wv6-86v2-598j","Warn: Project is vulnerable to: GHSA-rhx6-c78j-4q9w","Warn: Project is vulnerable to: GHSA-h7cp-r72f-jxh6","Warn: Project is vulnerable to: GHSA-v62p-rq8g-8h59","Warn: Project is vulnerable to: GHSA-566m-qj78-rww5","Warn: Project is vulnerable to: GHSA-hwj9-h5mp-3pm3","Warn: Project is vulnerable to: GHSA-7fh5-64p2-3v2j","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-m6fv-jmcg-4jfg","Warn: Project is vulnerable to: GHSA-cm22-4g7w-348p","Warn: Project is vulnerable to: GHSA-g4rg-993r-mgx7","Warn: Project is vulnerable to: GHSA-3jfq-g458-7qm9","Warn: Project is vulnerable to: GHSA-r628-mhmh-qjhw","Warn: Project is vulnerable to: GHSA-9r2w-394v-53qc","Warn: Project is vulnerable to: GHSA-5955-9wpr-37jh","Warn: Project is vulnerable to: GHSA-qq89-hq3f-393p","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36","Warn: Project is vulnerable to: GHSA-4wf5-vphf-c2xc","Warn: Project is vulnerable to: GHSA-jgrx-mgxx-jf9v","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-hh27-ffr2-f2jc","Warn: Project is vulnerable to: GHSA-rqff-837h-mm52","Warn: Project is vulnerable to: GHSA-8v38-pw62-9cw2","Warn: Project is vulnerable to: GHSA-hgjh-723h-mx2j","Warn: Project is vulnerable to: GHSA-jf5r-8hm2-f872","Warn: Project is vulnerable to: GHSA-wr3j-pwj9-hqq6","Warn: Project is vulnerable to: GHSA-4v9v-hfq4-rm2v","Warn: Project is vulnerable to: GHSA-9jgg-88mc-972h","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-6fc8-4gx4-v693","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-j4f2-536g-r55m","Warn: Project is vulnerable to: GHSA-r7qp-cfhv-p84w","Warn: Project is vulnerable to: GHSA-67mh-4wv8-2f99","Warn: Project is vulnerable to: GHSA-pfrx-2q88-qq97","Warn: Project is vulnerable to: GHSA-765h-qjxv-5f44","Warn: Project is vulnerable to: GHSA-f2jv-r9rf-7988","Warn: Project is vulnerable to: GHSA-7f5c-rpf4-86p8","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-rc47-6667-2j5j","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-8hfj-j24r-96c4","Warn: Project is vulnerable to: GHSA-wc69-rhjr-hc9g","Warn: Project is vulnerable to: GHSA-r683-j2x4-v87g","Warn: Project is vulnerable to: GHSA-r8f7-9pfq-mjmv","Warn: Project is vulnerable to: GHSA-px4h-xg32-q955","Warn: Project is vulnerable to: GHSA-6fx8-h7jm-663j","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-3965-hpx2-q597","Warn: Project is vulnerable to: GHSA-35q2-47q7-3pc3","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-7mwh-4pqv-wmr8","Warn: Project is vulnerable to: GHSA-25hc-qcg6-38wj","Warn: Project is vulnerable to: GHSA-qm95-pgcg-qqfq","Warn: Project is vulnerable to: GHSA-cqmj-92xf-r6r9","Warn: Project is vulnerable to: GHSA-vx3p-948g-6vhq","Warn: Project is vulnerable to: GHSA-wv8q-r932-8hc7","Warn: Project is vulnerable to: GHSA-8266-84wp-wv5c","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-7p7h-4mm5-852v","Warn: Project is vulnerable to: GHSA-rjf2-j2r6-q8gr","Warn: Project is vulnerable to: GHSA-6pw2-5hjv-9pf7","Warn: Project is vulnerable to: GHSA-4w2j-2rg4-5mjw","Warn: Project is vulnerable to: GHSA-mrgp-mrhc-5jrq","Warn: Project is vulnerable to: GHSA-7jxr-cg7f-gpgv","Warn: Project is vulnerable to: GHSA-xj72-wvfv-8985","Warn: Project is vulnerable to: GHSA-ch3r-j5x3-6q2m","Warn: Project is vulnerable to: GHSA-p5gc-c584-jj6v","Warn: Project is vulnerable to: GHSA-whpj-8f3w-67p5","Warn: Project is vulnerable to: GHSA-cchq-frgv-rjh5","Warn: Project is vulnerable to: GHSA-g644-9gfx-q4q4","Warn: Project is vulnerable to: GHSA-g3ch-rx76-35fx","Warn: Project is vulnerable to: GHSA-hc6q-2mpp-qw7j","Warn: Project is vulnerable to: GHSA-4vvj-4cpr-p986","Warn: Project is vulnerable to: GHSA-72mh-269x-7mh5","Warn: Project is vulnerable to: GHSA-h4j5-c7cj-74xg"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-22T22:33:02.826Z","repository_id":57153479,"created_at":"2025-08-22T22:33:02.826Z","updated_at":"2025-08-22T22:33:02.826Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31882886,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-16T09:23:21.276Z","status":"ssl_error","status_checked_at":"2026-04-16T09:23:15.028Z","response_time":69,"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":[],"created_at":"2024-11-11T02:11:17.723Z","updated_at":"2026-04-16T11:01:48.347Z","avatar_url":"https://github.com/rafgraph.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Polymorphic `as` prop API standard PoC\n\n\u003e If an API standard is something that the community is interested in pursuing, it probably makes sense for this to be it's own org run by the community.\n\nI'm a fan of the polymorphic `as` prop. I've been using it in [React Interactive](https://github.com/rafgraph/react-interactive) since 2016 (I think this was the first occurrence), and since then it's seen wide spread use with Styled Components helping to popularize it. With multiple libraries implementing the polymorphic `as` prop, I think composability needs to be added to polymorphism (the lack of composability has already caused multiple issues, for example [this Stitches issue](https://github.com/modulz/stitches/issues/448)). A standardized polymorphic API that libraries and components can implement would accomplish this, and would allow multiple polymorphic components to work together seamlessly.\n\nThe idea for this is something that I've been thinking about on and off for a bit, and was inspired by this clever [`styled` function PoC](https://codesandbox.io/embed/tender-gould-3q05k?file=/src/App.js) by [@jjenzz](https://github.com/jjenzz).\n\n---\n\n⚡️ [API ideas](#API-ideas) ⚡️ [Proof of concept npm package](#proof-of-concept-npm-package)\n\n**[Try out the proof of concept in CodeSandbox](https://codesandbox.io/s/github/rafgraph/polymorphic-as-prop-api/tree/main/demo?file=/src/PolymorphicAsArrayDemos.tsx)**\n\n---\n\n## API ideas\n\n### The API needs to define a standardized way to:\n\n- [Determine at runtime if a component implements the polymorphic API](#determine-at-runtime-if-the-API-is-implemented)\n- [Create multilevel polymorphism when the polymorphic API is implemented by both components](#create-multilevel-polymorphism)\n- [Merge two lines of polymorphism with a create polymorphic function (for example a `styled` function)](#merge-two-lines-of-polymorphism-with-a-create-function)\n\n---\n\n### Determine at runtime if the API is implemented\n\n\u003e In [@jjenzz](https://github.com/jjenzz)'s `styled` PoC she [calls the component function](https://codesandbox.io/s/tender-gould-3q05k?file=/src/App.js:192-272) with a dummy `as` prop, and then [checks the returned React Element's type](https://codesandbox.io/s/tender-gould-3q05k?file=/src/App.js:410-456) to accomplish this. This is some quality hacking 😄 but I don't believe it will scale (there are too many edge cases, e.g. side effects when calling the function component, React components in the form of forwarded ref and memo objects, etc).\n\nThe simplest solution is probably to add a property to the component indicating support for the API (the `polymorphicAsArray` name will be explained in the next section).\n\n```js\nconst MyPolymorphicComponent = (/*...*/) =\u003e {\n  /*...*/\n};\n\nMyPolymorphicComponent.polymorphicAsArray = true;\n\n// then at runtime in some other component we can do\nif (SomeComponent.polymorphicAsArray === true) {\n  // SomeComponent implements the polymorphic API\n}\n```\n\n---\n\n### Create multilevel polymorphism\n\nPolymorphic components can support multilevel polymorphism by accepting an array for the `as` prop.\n\n- A component that implements the `polymorphicAsArray` API would need to:\n  - Accept an array for the `as` prop\n  - Remove the first item from the `as` array and check if it supports the `polymorphicAsArray` API, if yes render it and pass through the remaining `as` array. If it doesn't implement the API, discard it, and check the next item. Repeat until either it finds a component that supports the `polymorphicAsArray` API, or it gets to the last item in the array, then always render it.\n  - Or render a predefined component that supports the `polymorphicAsArray` API and pass through the `as` prop to that component untouched.\n  - Set the `polymorphicAsArray` property on the component to `true`.\n  - Implement additional requirements of the API like `ref` forwarding (not done in the below example).\n\n**_Note that the PoC npm package exports a [helper function `polymorphicAsArrayUtil`](#polymorphicasarrayutil) that implements the `as` array logic._**\n\n```js\nconst PolymorphicComponent = ({ as = 'div', ...props }) =\u003e {\n  let As;\n  let asArray = [];\n\n  if (!Array.isArray(as)) {\n    As = as;\n  } else {\n    asArray = [...as];\n\n    while (asArray.length \u003e 0) {\n      const item = asArray.shift();\n      if (\n        // item implements polymorphicAsArray API\n        item?.polymorphicAsArray === true ||\n        // item is the last item in the `as` array\n        asArray.length === 0\n      ) {\n        As = item;\n        break;\n      }\n    }\n  }\n\n  // component logic...\n\n  // if the asArray is empty, don't pass it through\n  const passThroughAsProp = asArray.length === 0 ? undefined : asArray;\n\n  return \u003cAs {...props} as={passThroughAsProp} /\u003e;\n};\n\nPolymorphicComponent.polymorphicAsArray = true;\n```\n\n```js\n// usage\n// note that ButtonBase is also a polymorphic component\nconst App = () =\u003e (\n  \u003cMyPolymorphicComponent as={[ButtonBase, 'a']} href=\"#polymorphic\"\u003e\n    This renders MyPolymorphicComponent as a ButtonBase as an anchor tag\n  \u003c/MyPolymorphicComponent\u003e\n);\n```\n\n---\n\n### Merge two lines of polymorphism with a create function\n\n\u003e A create polymorphic function can come in different flavors for different use cases with additional functionality. For example, a `styled` function that implements the `polymorphicAsArray` API would be a create function. Also, a create function can optionally support a default props argument (using composition, not the to-be-deprecated `defaultProps` property).\n\n- A create polymorphic function that implements the `polymorphicAsArray` API and merges two lines of polymorphism would need to:\n  - Accept an `as` array as the first argument (additional arguments can be supported for specific functionality, e.g. a styles object, default props object, etc).\n  - Return a component that:\n    - Accepts an array for the `as` prop.\n    - Appends the `as` prop to the end of the `as` array argument passed to the create function.\n    - Renders the merged `as` array as describe above in [Create multilevel polymorphism](#create-multilevel-polymorphism) (render the first item that supports the `polymorphicAsArray` API and pass through the remaining array).\n    - Has the `polymorphicAsArray` property set to `true`.\n    - Implements additional requirements of the API like `ref` forwarding (not done in below example).\n\n**_Note that the PoC npm package exports a [helper function `polymorphicAsArrayUtil`](#polymorphicasarrayutil) that implements the `as` array logic._**\n\n```js\nconst createPolymorphic = (defaultAs = 'div') =\u003e {\n  const asArray = Array.isArray(defaultAs) ? [...defaultAs] : [defaultAs];\n\n  // polymorphic component that's returned from the create function\n  const PolymorphicComponent = ({ as, ...props }) =\u003e {\n    if (as !== undefined) {\n      // append the `as` prop to the end of the `defaultAs` argument\n      Array.isArray(as) ? asArray.push(...as) : asArray.push(as);\n    }\n\n    // same logic as in the above \"Create multilevel polymorphism\" example\n    let As;\n    while (asArray.length \u003e 0) {\n      const item = asArray.shift();\n      if (\n        // item implements polymorphicAsArray API\n        item?.polymorphicAsArray === true ||\n        // item is the last item in the `as` array\n        asArray.length === 0\n      ) {\n        As = item;\n        break;\n      }\n    }\n\n    const passThroughAsProp = asArray.length === 0 ? undefined : asArray;\n\n    return \u003cAs {...props} as={passThroughAsProp} /\u003e;\n  };\n\n  PolymorphicComponent.polymorphicAsArray = true;\n  return PolymorphicComponent;\n};\n```\n\n```js\n// usage\n// note that Interactive is also a polymorphic component\nconst ButtonBase = createPolymorphic([Interactive, 'button']);\n\nconst ButtonLink = createPolymorphic([ButtonBase, 'a']);\n\nconst ButtonLinkWithClosedApi = ({ disabled = false, href }) =\u003e (\n  \u003cButtonLink\n    as={disabled ? 'span' : undefined}\n    href={disabled ? undefined : href}\n  /\u003e\n);\n```\n\n---\n\n## Proof of concept npm package\n\nThe `polymorphic-as` proof of concept npm package exports 3 functions:\n\n- [`polymorphicAsArrayUtil`](#polymorphicasarrayutil) handles the logic of merging `as` arrays, selecting the `As` to render, and creating the `passThroughAsProp`.\n- [`createPolymorphic`](#createpolymorphic) is a generic create polymorphic function that accepts a default props object.\n- [`styled`](#styled) is a create polymorphic function that composes component styles.\n\n---\n\n**[Try out the proof of concept in CodeSandbox](https://codesandbox.io/s/github/rafgraph/polymorphic-as-prop-api/tree/main/demo?file=/src/PolymorphicAsArrayDemos.tsx)**\n\n---\n\n### Install\n\n```shell\nnpm i --save polymorphic-as\n```\n\n---\n\n### `polymorphicAsArrayUtil`\n\nHelper function to handle the logic of merging `as` arrays, selecting the `As` to render, and creating the `passThroughAsProp`.\n\n**_`polymorphicAsArrayUtil` should be used by every component and create function that implements the `polymorphicAsArray` API._**\n\n```js\nimport { polymorphicAsArrayUtil } from 'polymorphic-as';\n\nconst AsAndPassThroughAsProp = polymorphicAsArrayUtil({\n  defaultAs, // the `as` argument from a create polymorphic function, optional\n  as, // the `as` prop passed to a polymorphic component, optional\n});\n\nconst {\n  As, // \u003cAs /\u003e to render\n  passThroughAsProp, // `as` prop to pass to As, \u003cAs as={passThroughAsProp} /\u003e\n} = AsAndPassThroughAsProp;\n```\n\n```js\n// using polymorphicAsArrayUtil in a polymorphic component\n\nimport { polymorphicAsArrayUtil } from 'polymorphic-as';\n\nconst MyPolymorphicComponent = React.forwardRef(\n  ({ as = 'div', ...props }, ref) =\u003e {\n    const { As, passThroughAsProp } = polymorphicAsArrayUtil({ as });\n\n    // component logic...\n\n    return \u003cAs {...props} as={passThroughAsProp} ref={ref} /\u003e;\n  },\n);\n\nMyPolymorphicComponent.polymorphicAsArray = true;\n```\n\n```js\n// using polymorphicAsArrayUtil in a create function\n\nexport const myPolymorphicCreateFunction = (defaultAs) =\u003e {\n  // the polymorphic component that's returned from the create function\n  const PolymorphicComponent = React.forwardRef(({ as, ...props }, ref) =\u003e {\n    const { As, passThroughAsProp } = polymorphicAsArrayUtil({\n      defaultAs,\n      as,\n    });\n\n    return \u003cAs {...props} as={passThroughAsProp} ref={ref} /\u003e;\n  });\n\n  PolymorphicComponent.polymorphicAsArray = true;\n\n  return PolymorphicComponent;\n};\n```\n\n---\n\n### `createPolymorphic`\n\n`createPolymorphic` is a generic create function that returns a polymorphic component which implements the `polymorphicAsArray` API. It also accepts a default props object as a second argument.\n\n```js\nimport { createPolymorphic } from 'polymorphic-as';\n\nconst PolymorphicComponent = createPolymorphic(as | as[], defaultPropsObject);\n```\n\n```js\nimport { createPolymorphic } from 'polymorphic-as';\n\nconst MyButton = createPolymorphic('button', { className: 'my-button' });\n\nconst MyButtonLink = createPolymorphic([MyButton, 'a']);\n```\n\n---\n\n### `styled`\n\n\u003e Adapted from this [`styled` function PoC](https://codesandbox.io/embed/tender-gould-3q05k?file=/src/App.js) by [@jjenzz](https://github.com/jjenzz).\n\n\u003e The intention of the `styled` function PoC is to demonstrate what's possible if a production `styled` function (e.g. Stitches' `styled`) implemented the `polymorphicAsArray` API.\n\n`styled` is a create function that composes component styles (using inline styles) and returns a styled polymorphic component. It also accepts a default props object as a third argument.\n\n```js\nimport { styled } from 'polymorphic-as';\n\nconst StyledPolymorphicComponent = styled(as | as[], stylesObject, defaultPropsObject);\n```\n\n```js\n// button example\n\nimport { styled } from 'polymorphic-as';\n\nconst ButtonBase = styled('button', {\n  padding: '10px 20px',\n  border: '1px solid',\n  borderRadius: '1000px',\n});\n\nconst ButtonLink = styled([ButtonBase, 'a']);\n\nconst GreenButton = styled(ButtonBase, { color: 'green' });\n\nconst SubmitButton = styled(\n  GreenButton,\n  { fontWeight: 'bold' },\n  { type: 'submit' },\n);\n\nconst TopOfPageButton = styled(\n  [ButtonBase, 'a'],\n  { padding: '5px 10px' },\n  { href: '#top', children: 'Top of Page' }, // default props\n);\n```\n\n```js\n// text input example\n\nimport { styled } from 'polymorphic-as';\n\nconst TextInput = styled(\n  'input',\n  { border: '1px solid', borderRadius: '4px' },\n  { type: 'text' }, // default props\n);\n```\n\n---\n\n### TypeScript\n\nThe proof of concept code is written is TypeScript but with a generous use of `any` (there are no inferred types from the `as` prop).\n\nThe `polymorphic-as` package does export a `PolymorphicAsArrayForwardRefComponent` type that can be used to type components that implement the polymorphic as array API, and a `PolymorphicAsArrayProps` type that the props interface can extend.\n\n```ts\nimport {\n  polymorphicAsArrayUtil,\n  PolymorphicAsArrayForwardRefComponent,\n  PolymorphicAsArrayProps,\n} from 'polymorphic-as';\n\ninterface MyPolymorphicComponentProps extends PolymorphicAsArrayProps {\n  someProp?: string;\n}\n\n// prettier-ignore\nconst MyPolymorphicComponent:\n  PolymorphicAsArrayForwardRefComponent\u003cMyPolymorphicComponentProps\u003e =\n  React.forwardRef(\n  ({ as = 'div', someProp, ...props }, ref) =\u003e {\n    const { As, passThroughAsProp } = polymorphicAsArrayUtil({ as });\n\n    // component logic...\n\n    return \u003cAs {...props} ref={ref} /\u003e;\n  },\n);\n\nMyPolymorphicComponent.polymorphicAsArray = true;\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafgraph%2Fpolymorphic-as-prop-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frafgraph%2Fpolymorphic-as-prop-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafgraph%2Fpolymorphic-as-prop-api/lists"}