solidjs / solid

A declarative, efficient, and flexible JavaScript library for building user interfaces.
https://solidjs.com
MIT License
32.37k stars 925 forks source link

Polymorphic Components #701

Closed nsaunders closed 3 years ago

nsaunders commented 3 years ago

Hi, is it possible to implement polymorphic components using SolidJS? For anyone who might be unaware, polymorphic components are a common pattern in React, where a prop like as or component allows you to control what HTML tag is used in the rendered output, i.e. <Link as="a" href="...">...</Link> or <Link as="button" type="submit">...</Link>.

ryansolid commented 3 years ago

Yeah, we use this trick with our styled components implementation. The easiest way is to use the <Dynamic> tag internally. https://www.solidjs.com/tutorial/flow_dynamic

To clarify dynamic can take a string tag as well like a as the component={}

lxsmnsyc commented 3 years ago

^Like what he said.

If you're using TypeScript, however, it gets very complex. You can refer to solid-headless' way of type inference for Polymorphic components: https://github.com/LXSMNSYC/solid-headless/blob/main/packages/solid-headless/src/utils/dynamic-prop.ts

nsaunders commented 3 years ago

Wonderful, thanks @ryansolid and @LXSMNSYC for the responses!

fabien-ml commented 2 years ago

Hi, for other people that want to do it, here is a basic implementation adapted from this react version :

import { Dynamic, render } from "solid-js/web";
import { Component, ComponentProps, JSX, splitProps } from "solid-js";

type ElementType = keyof JSX.IntrinsicElements | Component<any>;

type BoxProps<T extends ElementType> = {
    as?: T,
    children?: JSX.Element
}

function Box<T extends ElementType = "div">(props: BoxProps<T> & Omit<ComponentProps<T>, keyof BoxProps<T>>) {
    const [local, others] = splitProps(props, ["as"]);
    const component = local.as || "div"

    return <Dynamic component={component} {...others} />
}

Usage example :

function RedThing(props: { text: string }) {
  return <strong style="color: red">{props.text}</strong>;
}

function App() {

  let inputRef : HTMLInputElement | null = null;

  const focusInput = () => {
    inputRef?.focus()
  }

  return (
    <Box as="main">
      <Box as={RedThing} text="hello" />
      <Box as="a" href="https://solidjs.com">go to solidjs website</Box>
      <Box as="input" ref={inputRef} />
      <Box as="button" onClick={focusInput}>Focus input</Box>
    </Box>
  );
}

render(() => <App />, document.getElementById("app"));