Say I have a `Card` component, How can I have a `Card.Header` component?

Say I have a Card component, How can I have a Card.Header component?
32 Replies
REEEEE
REEEEE2y ago
Card.Header = HeaderComponent
Mathieu
MathieuOP2y ago
const Card: ParentComponent<Props> = (props) => {
return //...
};
Card.Header = Header;
export default Card;
const Card: ParentComponent<Props> = (props) => {
return //...
};
Card.Header = Header;
export default Card;
TS error:
Property 'Header' does not exist on type 'ParentComponent<Props>'
it works though, thanks!! just gotta fix that TS error now
jesseb34r
jesseb34r2y ago
kobalte has good examples of this pattern and has good typing although that solution is a little different in kobalte it is done with index files
// ~/components/Card/index.ts
import { Card } as Root from './Card.tsx';
import { CardHeader } as Header from './CardHeader.tsx';

export { Root, Header };
// ~/components/Card/index.ts
import { Card } as Root from './Card.tsx';
import { CardHeader } as Header from './CardHeader.tsx';

export { Root, Header };
thetarnav
thetarnav2y ago
const Root: ParentComponent<Props> = (props) => {
return //...
};
(Root as any).Header = Header;
export const Card = Root as typeof Root & { header: typeof Header }
const Root: ParentComponent<Props> = (props) => {
return //...
};
(Root as any).Header = Header;
export const Card = Root as typeof Root & { header: typeof Header }
jesseb34r
jesseb34r2y ago
you don't need to type cast as any there are better ways to do that you could also achieve the index.ts behavior with a default export from a component file like how radix does it actually, they don't default export, they just export an object
jesseb34r
jesseb34r2y ago
jesseb34r
jesseb34r2y ago
you can also type a composite component and add that to the typing:
const CardHeader: Component<{ title: string }> = (props) => (
<h2>{props.title}</h2>
)

const CardFooter: Component<{ content: string }> = (props) => (
<div>{props.content}</div>
)

type CardComposite = {
Header: typeof CardHeader;
Footer: typeof CardFooter;
}

const Card: Component<CardProps> & CardComposite = (props) => {
/* card component here */
}

Card.Header = CardHeader;
Card.Footer = CardFooter;

export { Card };
const CardHeader: Component<{ title: string }> = (props) => (
<h2>{props.title}</h2>
)

const CardFooter: Component<{ content: string }> = (props) => (
<div>{props.content}</div>
)

type CardComposite = {
Header: typeof CardHeader;
Footer: typeof CardFooter;
}

const Card: Component<CardProps> & CardComposite = (props) => {
/* card component here */
}

Card.Header = CardHeader;
Card.Footer = CardFooter;

export { Card };
this is the nicer way of doing it without typecasting to any
Mathieu
MathieuOP2y ago
Obviously the solution that thetarnav shared is very simple to understand. What you have shown is something that ends up with: export { Root, Header }; I don't see how that allows Card.Header. In the solution of thetarnav there is an assignment: (Root as any).Header = Header; which makes it clear I can now call Card.Header
jesseb34r
jesseb34r2y ago
in the index.ts example, you are exporting an object as default from a folder so you do a default import and it gives you the object
Mathieu
MathieuOP2y ago
Ah right
jesseb34r
jesseb34r2y ago
// ~/components/Card/index.ts
import { CardHeader } as Header from './card-header.tsx';
import { CardRoot } as Root from './card-root.tsx';

export { Root, Header };


// ~/routes/index.tsx
import Card from '~/components/Card'

export default function HomePage() {
return (
<Card.Root>
<Card.Header>{/* content */}</Card.Header>
{/* content */}
</Card.Root>
)
}
// ~/components/Card/index.ts
import { CardHeader } as Header from './card-header.tsx';
import { CardRoot } as Root from './card-root.tsx';

export { Root, Header };


// ~/routes/index.tsx
import Card from '~/components/Card'

export default function HomePage() {
return (
<Card.Root>
<Card.Header>{/* content */}</Card.Header>
{/* content */}
</Card.Root>
)
}
Mathieu
MathieuOP2y ago
so you literally coded it for me haha @jesseb34r one thing. I need to write Card.Root instead of just Card
jesseb34r
jesseb34r2y ago
yeah, hard to get around that
jesseb34r
jesseb34r2y ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
jesseb34r
jesseb34r2y ago
here is an example with code this one allows for named import by doing export const Card = { Root, Header } the composite method I showed might allow that? let me tinker with it for a sec
Otonashi
Otonashi2y ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
Otonashi
Otonashi2y ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
jesseb34r
jesseb34r2y ago
yeah the composite method allows for not needing root as does @otonashi9 's examples
jesseb34r
jesseb34r2y ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
jesseb34r
jesseb34r2y ago
the reason why your second example works but the first try in the question above didn't is because of
const Card: ParentComponent<Props> = (props) => {}
const Card: ParentComponent<Props> = (props) => {}
vs
function Card(props: ParentProps<Props>) {}
function Card(props: ParentProps<Props>) {}
right? @otonashi9
Otonashi
Otonashi2y ago
yeah
jesseb34r
jesseb34r2y ago
that's why you need the composite typing for the arrow function version or i guess you could do it without it
const Card = (props: ParentProps<Props>) => {}
const Card = (props: ParentProps<Props>) => {}
wouldn't type error on assigning Card.Header https://playground.solidjs.com/anonymous/3a771f71-814c-4368-9a40-199961aeae73
Mathieu
MathieuOP2y ago
@otonashi9 one thing is that for all my other components I have default imports, like:
import Button from 'my-ui-lib';
import DatePicker from 'my-ui-lib';
import TextField from 'my-ui-lib';
import { Card } from 'my-ui-lib';
import Button from 'my-ui-lib';
import DatePicker from 'my-ui-lib';
import TextField from 'my-ui-lib';
import { Card } from 'my-ui-lib';
- am I suffering from OCD? - if not, better to move everything as non default export? is it better? - if not, go for thetarnav solution (which allows default import)?
jesseb34r
jesseb34r2y ago
you can just export default
Otonashi
Otonashi2y ago
^
Mathieu
MathieuOP2y ago
soz then. Gonna try it out!
jesseb34r
jesseb34r2y ago
I prefer named imports, idk why, I had a reason when I made the decision to be consistent about that like a year ago I feel like it was a good reason then but I've forgotten it now
Otonashi
Otonashi2y ago
named imports are easier to refactor, can't remember what else there was
jesseb34r
jesseb34r2y ago
but I also have to just give up on any OCD organization because my main project is not mine but is a joint project with someone else and they really want imports to be full path, no relative, and use the organization/repo/* syntax except that in this case the org and repo both have the same name it's ugly
jesseb34r
jesseb34r2y ago
Mathieu
MathieuOP2y ago
Yeah there are advantages: 1. Say you export foo, import { bar } from Foo will fail but import bar from Foo will not fail => better for refactoring => better for searching things in a reliable way 2. You may want to export other things int he future from the same file. => with default export sometimes you'd need to convert to exporting an object. That said, I still like default exports where I'm confident I won't need to refactor names, like a generic "Button" component...
jesseb34r
jesseb34r2y ago
I think another thing was I wanted it to be easier to import multiple things from the same file and didn't want to have to choose a default export also I like arrow functions and you can't do export default const MyComponent = () => {} you have to do a two liner
const MyComponent = () => {};
export default MyComponent;
const MyComponent = () => {};
export default MyComponent;
which is not nearly so nice as just
export const MyCompenent = () => {};
export const MyCompenent = () => {};
Want results from more Discord servers?
Add your server