I have components like this
import React from 'react';
import styled from '../../styled-components';
const StyledInput = styled.input`
display: block;
padding: 5px 10px;
width: 50%;
border: none;
border-radius: 10px;
outline: none;
background: ${(props) => props.theme.color.background};
font-family: 'Roboto', sans-serif;
`
;
export const Input = () => {
return <StyledInput placeholder="All notes"></StyledInput>
}
I want to place them as property on "index" component like this
import styled from 'styled-components';
import { Input } from './Input';
const NoteTags:any = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
width: 20%;
height: 5%;
border-bottom: 2px solid ${(props) => props.theme.color.background}; `;
NoteTags.Input = Input;
export default NoteTags;
So then I can use them like this
import React from 'react';
import NoteTags from '../../blocks/note-tags';
const ComponentNoteTags = () => {
return (
<React.Fragment>
<NoteTags.Input></NoteTags.Input>
</React.Fragment>
)
}
export default ComponentNoteTags;
The problem with this is that property Input
doesn't exist on NoteTags
so typescript gives me an error. I can solve it by setting type any
to NoteTags
but I'm not sure that it is the best way.
Is there a better way to fix it?
To pass props to React components created with styled-components, we can interpolate functions into the string that creates the component. We create the ArrowStyled component with the styled. div tag. Then string that we pass into the tag has the rotate value set from a prop.
Update
I found it much better to use a type helper to add extra properties into a component's variable:
// type-helper.ts
export function withProperties<A, B>(component: A, properties: B): A & B {
Object.keys(properties).forEach(key => {
component[key] = properties[key]
});
return component as A & B;
}
Note: @Allan points out in the comment, type assertion might be necessary like so:
(component as any)[key] = (properties as any)[key]
to avoid typescript complaining about
Element implicitly has an 'any' type because type '{}' has no index signature
.
We're just passing each property in properties
to component
and give it a joined type of both. Then we can 'glue' sub components into a main component as follow:
// component/index.ts
import MainComponent from './MainComponent';
import SubComponent1 from './SubComponent1';
import SubComponent2 from './SubComponent2';
import { withProperties } from '../type-helper.ts';
export withProperties(MainComponent, { SubComponent1, SubComponent2 })
and use it as usual
import MainComponent from './Component';
export default () => (
<MainComponent>
<SubComponent1 />
<SubComponent2 />
</MainComponent>
)
This approach works with normal React component as well & not just styled components one, so I think it's better.
Here's a codesandbox demo with both approaches I've shared, one of each use your code.
Previous answer
Assuming you're using the latest packages:
"styled-components": "^4.1.3",
"@types/styled-components": "^4.1.10",
You can create an interection type that include both the type of the parent & child components:
import styled, { StyledComponent } from 'styled-components'
import ChildComponent from '...'
type Component = StyledComponent<'div', any> & {
ChildComponent: /* your component type */
}
interface IProps {
...
}
// create styled component per usual
const MyComponent = styled.div<IProps>`
...
` as Component
MyComponent.ChildComponent = ChildComponent;
Here's the StyledComponent
definition:
export type StyledComponent<
C extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
T extends object, // theme interface
O extends object = {}, // props interface
A extends keyof any = never
> = string & StyledComponentBase<C, T, O, A>;
You can use it as StyledComponent<C, T, O, A>
or StyledComponent<C, T>
I've managed to get it working by defining the call signatures of the styled component and the additional properties you want to add. Typescript will complain that Item
doesn't exist on List
, so I had to use Object Assign to create the styled component and assign the property simultaneously.
import styled from 'styled-components'
import { PropsWithChildren, ReactElement } from 'react'
interface ListOverload {
({ children }: PropsWithChildren<{}>): ReactElement
Item({ children }: PropsWithChildren<{}>): ReactElement
}
export const Item = styled.li(
({ theme }) => css`
padding: ${theme.space.md};
border-bottom: 1px solid ${theme.colors.greyLight};
`
)
export const List: ListOverload = Object.assign(
styled.ul`
list-style: none;
padding: 0;
margin: 0;
`,
{ Item }
)
Then you can use like this in your JSX:
<List>
<List.Item>Todo 1</List.Item>
<List.Item>Todo 2</List.Item>
<List.Item>Todo 3</List.Item>
</List>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With