Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How do I provide props to styled elements in Typescript? #630

Closed
tal opened this issue Mar 30, 2017 · 85 comments

Comments

Projects
None yet
@tal
Copy link

commented Mar 30, 2017

I looked at the docs and other than creating a whole empty component purely for providing a definition of props I'm not sure of if it's possible to provide custom props to a styled element other than theme.

Example

image

For the above example I don't know how to make it not error.

@kitten

This comment has been minimized.

Copy link
Member

commented Mar 30, 2017

@tal Assuming that TypeScript infers the generics correctly — Disclaimer 😉 I haven't used SC with TypeScript yet — you'll just need to type your interpolation argument:

const Input = styled.input`
  border: ${(p: YourProps) => p.invalid ? 'red' : 'blue'};
`
@tal

This comment has been minimized.

Copy link
Author

commented Mar 30, 2017

@mxstbr mxstbr added the bug 🐛 label Apr 1, 2017

@mxstbr

This comment has been minimized.

Copy link
Member

commented Apr 1, 2017

/cc @styled-components/typers any ideas?

@jupl

This comment has been minimized.

Copy link

commented Apr 2, 2017

EDIT: Holdup. What I had just happened to fit my usecase.

This currently seems to be the best approach, albeit a bit wordy.

@giladaya

This comment has been minimized.

Copy link

commented Apr 5, 2017

@philpl 's approach worked for me

@beshanoe

This comment has been minimized.

Copy link

commented Jun 7, 2017

I'm using v2 and found this way more convinient for plain containers:

function stuc<T>(El: string = 'div') {
  type PropType = T & { children?: JSX.Element[], className?: string }
  return (props: PropType) => <El className={props.className}>{props.children}</El>
}

const Container = stuc<{customProp?: boolean}>()

const StyledContainer = styled(Container)`
  background: ${p => p.customProp ? 'red' : 'blue'};
`
@patrick91

This comment has been minimized.

Copy link
Contributor

commented Jun 7, 2017

@beshanoe hopefully we'll have a way to do this in TS (see: microsoft/TypeScript#11947)

@mxstbr

This comment has been minimized.

Copy link
Member

commented Jun 30, 2017

Going to close this as there isn't much we can do right now about this, thanks for discussing!

@mxstbr mxstbr closed this Jun 30, 2017

@alloy

This comment has been minimized.

Copy link

commented Jul 14, 2017

This is what I use at the moment:

import styled, { StyledFunction } from "styled-components"

interface YourProps {
  invalid: boolean
}

const input: StyledFunction<YourProps & React.HTMLProps<HTMLInputElement>> = styled.input

const Input = input`
  border: ${p => p.invalid ? 'red' : 'blue'};
`
@david-mk

This comment has been minimized.

Copy link

commented Jul 22, 2017

@alloy's solution worked great for me

I made a function wrapper for it that doesn't really do anything other than let typescript know of the types, but I think it makes the code a little more terse

import React from 'react'
import styled, { StyledFunction } from 'styled-components'

function styledComponentWithProps<T, U extends HTMLElement = HTMLElement>(styledFunction: StyledFunction<React.HTMLProps<U>>): StyledFunction<T & React.HTMLProps<U>> {
    return styledFunction
}

interface MyProps {
    // ...
}

const div = styledComponentWithProps<MyProps, HTMLDivElement>(styled.div)
@atfzl

This comment has been minimized.

Copy link

commented Jul 22, 2017

Extending @david-mk's method to get theme types as shown in https://www.styled-components.com/docs/api#typescript

// themed-components.ts
import * as styledComponents from 'styled-components';
import { ThemedStyledComponentsModule } from 'styled-components';

import { ITheme } from 'theme'; // custom theme

type StyledFunction<T> = styledComponents.ThemedStyledFunction<T, ITheme>;

function withProps<T, U extends HTMLElement = HTMLElement>(
  styledFunction: StyledFunction<React.HTMLProps<U>>,
): StyledFunction<T & React.HTMLProps<U>> {
  return styledFunction;
}

const {
  default: styled,
  css,
  injectGlobal,
  keyframes,
  ThemeProvider,
} = styledComponents as ThemedStyledComponentsModule<ITheme>;

export { css, injectGlobal, keyframes, ThemeProvider, withProps };
export default styled;
// component

import styled, { withProps } from 'themed-components';

interface IProps {
  active?: boolean;
}

const Container = withProps<IProps, HTMLDivElement>(styled.div)`
  height: 100%;
  padding-right: 52px;
  display: flex;
  justify-content: center;
  align-items: center;
  color: ${props => props.theme.colors.black4};
  background-color: ${props => props.active ? props.theme.colors.white : props.theme.colors.grey};
`;
@Igorbek

This comment has been minimized.

Copy link
Contributor

commented Jul 23, 2017

Another option which allows to bypass explicit element type specification, but looking a bit awkward:

import { ThemedStyledFunction } from 'styled-components';

export const withProps = <U>() =>
    <P, T, O>(
        fn: ThemedStyledFunction<P, T, O>
    ): ThemedStyledFunction<P & U, T, O & U> => fn;

so later:

interface ButtonProps {
    active: boolean;
}

//                                           / note () here
export const Button = withProps<ButtonProps>()(styled.div)`
  color: ${p => p.active ? 'red' : 'black'}
`;
@jimthedev

This comment has been minimized.

Copy link
Contributor

commented Oct 18, 2017

@Igorbek Have you noticed that vscode's typescript styled plugin seems to not work when using any of these syntaxes?

Edit:

In the mean time I've aliased styled to themed and then add props to each of the types I want to use. Could probably be optimized more to be less ugly:

import * as themed from 'styled-components';

//...

interface IHeadingProps {
  active?: boolean;
}

const styled = {
  div: withProps<IHeadingProps>()(themed.div)
};

//...

export const Heading = styled.div`
  font-size: 22px;
`;

Edit: Opened issue here: microsoft/typescript-styled-plugin#21

@jimthedev

This comment has been minimized.

Copy link
Contributor

commented Oct 18, 2017

Also @Igorbek do you have a full example of your Jul 23 post? TS keeps thinking the types are JSX on that one for me and complaining about implicit any.

EDIT: Nevermind, I rewrote it this way and it worked

function withProps<U>() {
    return <P,T,O>(
        fn: ThemedStyledFunction<P, T, O>
    ): ThemedStyledFunction<P & U, T, O & U> => fn
}
@Lucasus

This comment has been minimized.

Copy link

commented Nov 3, 2017

@Igorbek I needed to slightly change your withProps function after upgrading to TypeScript 2.6 with strict mode enabled

Reason: there's new "Strict function types" behavior in TS 2.6 which forbids returning fs function with type parameters extended in such a way (as it's unsound).

My solution was to explicitly cast fn to ThemedStyledFunction<P & U, T, O & U>:

const withProps = <U>() =>
    <P, T, O>(
        fn: ThemedStyledFunction<P, T, O>
    ) => fn as ThemedStyledFunction<P & U, T, O & U>;

Other option is to set strictFunctionTypes to false in tsconfig.json

@FourwingsY

This comment has been minimized.

Copy link

commented Dec 19, 2017

It seems this methods works too.

const Component = styled<Props, 'div'>('div')`
  color: ${color.primary}
`

And this provides better code-highlighting in code editors, babel-plugins display name supports.

@seivan

This comment has been minimized.

Copy link

commented Dec 19, 2017

@FourwingsY I think the documentation mentions you'd want to avoid that

We encourage you to not use the styled('tagname') notation directly. Instead, rely on the styled.tagname methods like styled.button. We define all valid HTML5 and SVG elements. (It's an automatic fat finger check too)

@FourwingsY

This comment has been minimized.

Copy link

commented Dec 19, 2017

@seivan I know, but then i need to wait until webstorm plugin and babel plugin OR styled-components itself supports for withProps methods.

@seivan

This comment has been minimized.

Copy link

commented Dec 19, 2017

@FourwingsY I wonder if this would work for you

interface SectionProps {
  // see https://github.com/Microsoft/TypeScript/issues/8588
  children?: React.ReactChild;
  className?: string;
}

class PlainButton extends React.PureComponent<SectionProps, {}> {
  render() {
    return (
      <button className={this.props.className}>
        {this.props.children}
      </button>
    );
  }
}
const Button = styled(PlainButton)`
    /* This renders the buttons above... Edit me! */
    
    border: 2px solid red;
    background : "red";
    color: palevioletred;

`

In other words, you could just build a regular component, and run it through styled-components to get what you want.

@FourwingsY

This comment has been minimized.

Copy link

commented Dec 19, 2017

@seivan Thanks, I've tried that.
when I tried to apply styled(component) for complex component - that should split nested fragments into another styled components - like this

class ComplexComponent extends Component<Props, State> {
  renderSomeComponent() {
    return (
      <SomeStyledComponent>
        {...}
      </SomeStyledComponent>
    )
  }
  render() {
    <div className={this.props.className}>
      {this.renderSomeComponent()}
      <OtherStyledComponent />
      {...otherFragments}
    </div>
  }
}

then I need to split SomeStyledComponent and OtherStyledComponent to React.Component which was just result of styled.div or something.

It drives me to make too much classes. What i want is styling with typed props, I do not want to split a component into several components just for this.

@seivan

This comment has been minimized.

Copy link

commented Dec 19, 2017

@FourwingsY I don't think I follow. Why does it matter how many you got, as you would have to define those smaller components anyway? Unless I'm missing something, nothing would change and it sounds like these are just internal tiny components that shouldn't get exported anyway.

But I am just getting started, so most likely I'm missing the point completely.

@FourwingsY

This comment has been minimized.

Copy link

commented Dec 19, 2017

@seivan Maybe it's my prefer code style... Yes I might re-think about my codes. Thanks anyway.

@seivan

This comment has been minimized.

Copy link

commented Dec 19, 2017

@FourwingsY Let me know if you figure it out. I still haven't figured out a nice way to pass props.

const Button = styled(PlainButton)`    
    border: 2px solid red;
    background : red;
    color: palevioletred;
      ${props => props.primary && css`
          background: green;
          color: orange;
       `}
`
@goldcaddy77

This comment has been minimized.

Copy link

commented Jan 3, 2018

Anybody else running into some issues with @david-mk / @atfzl 's solutions above:

import * as styledComponents from 'styled-components';
// tslint:disable
import { ThemedStyledComponentsModule } from 'styled-components';

import { ThemeProps } from './theme';

export type StyledFunction<T> = styledComponents.ThemedStyledFunction<T, ThemeProps>;

function componentWithProps<T, U extends HTMLElement = HTMLElement>(
  styledFunction: StyledFunction<React.HTMLProps<U>>
): StyledFunction<T & React.HTMLProps<U>> {
  return styledFunction;
}

const {
  default: styled,
  css,
  injectGlobal,
  keyframes,
  ThemeProvider
} = styledComponents as ThemedStyledComponentsModule<ThemeProps>;

export { css, injectGlobal, keyframes, ThemeProvider, componentWithProps };
export default styled;

I'm getting this on the return styledFunction; line:

Type 'ThemedStyledFunction<HTMLProps<U>, ThemeProps, HTMLProps<U>>' is not assignable to type 'ThemedStyledFunction<T & HTMLProps<U>, ThemeProps, T & HTMLProps<U>>'.
  Type 'HTMLProps<U>' is not assignable to type 'T & HTMLProps<U>'.
    Type 'HTMLProps<U>' is not assignable to type 'T'.

Versions

  • Typescript: 2.6.2
  • React: 15.6.2
@david-mk

This comment has been minimized.

Copy link

commented Jan 11, 2018

@goldcaddy77 as @Lucasus pointed out, ts 2.6 introduced strict function types. So I did a function form of his

export function withProps<U>() {
    return <P, T, O>(
        fn: ThemedStyledFunction<P, T, O>
    ): ThemedStyledFunction<P & U, T, O & U> => fn as ThemedStyledFunction<P & U, T, O & U>
}
@FourwingsY

This comment has been minimized.

Copy link

commented Jan 19, 2018

I think styled-components have to decide one of this withProps methods and officially support it.
For Other 3rd party plugins(editors, or editor plugins, etc.) to support code highlighting.

@mattleonowicz

This comment has been minimized.

Copy link

commented Aug 8, 2018

None of the solutions here allowed for Webstorm autocompletion to work while using the component.
The docs mention a caveat:

To stateless components and have typechecking for the props you'll need to define the component alongside with its type. This is not special to styled-components, this is just how React works

Creating an additional functional component like suggested is a bit cumbersome, so I've written a helper function:

const component: <Props>(Tag: string) => React.SFC<Props & { className?: string }> = Tag => props => <Tag className={props.className}>{props.children}</Tag>;

which allows me to create styled components like so:

import styled, { css } from 'styled-components';

interface Props {
  color?: string;
}

const MyComponent = styled(component<Props>('div'))`
  ${props => props.color && css`
    color: ${props.color};
  `};
`;

and WebStorm autocomplete works for me.
Hope it helps someone.

@Toanzzz

This comment has been minimized.

Copy link

commented Nov 4, 2018

I just stump upon this issue, and this one work for me:

type StyledMyComponentProps = { mainColor: string, subColor: string }
const StyledMyComponent = styled(MyComponent).attrs<StyledMyComponentProps>({})`
  color: white;
  .main { color: ${p => p.mainColor}; }
  .sub { color: ${p => p.subColor}; }
`
const Input = styled.input.attrs<{ invalid: boolean }>({})`
  border: ${p => p.invalid ? 'red' : 'blue'};
`

Basically, you just attached empty attributes object to your styled component, and give it a type

@ctsin

This comment has been minimized.

Copy link

commented Nov 19, 2018

This works for me with TypeScript 2.9.2 and Styled Components 3.3.0


interface IMyImageProps {
  border?: string,
  children?: any
}

const MediumButton = styled.button<IMyImageProps>`
  background: aliceblue;
  color: snow;
  border: 4px solid ${p  => p.border ? 'red' : 'blue'};

But lint error in TS3.1 & styled-components 4.

@karolfalkiewicz

This comment has been minimized.

Copy link

commented Nov 20, 2018

Tried every possible way of using this. Still no props validation when I try to use styled-component with attached type to it. Was hopping to use styled-components-ts but not supporting v4 right now.

@jacob-ebey

This comment has been minimized.

Copy link

commented Nov 26, 2018

@karolfalkiewicz, someone submited a PR I accepted.

@Igorbek

This comment has been minimized.

Copy link
Contributor

commented Nov 30, 2018

@karolfalkiewicz can you give an example of what you used? maybe a simple repository reproducing it?

@Albert-Gao

This comment has been minimized.

Copy link

commented Dec 5, 2018

The examples on the doc won't work anymore. And after inspecting the typing and its test. Finally get working with latest styled component v4

versions:
"@types/react": "^16.7.6",
"@types/react-dom": "^16.0.9",
"@types/styled-components": "^4.1.0",
"styled-components": "^4.1.1",
"typescript": "^3.1.6"

I can only get it working via the following way:

interface IStyledInput {
  type: string;
  status: ValueOfValidationStatus;
  onChange?: Function;
}

const StyledInput = styled('input')<IStyledInput>`
    ${({ theme, status }) => {
      // Now you get the theme and status( from IStyledInput)
      }
    }};
` as React.FC<IStyledInput>;

You must cast it to React.FC at last, otherwise TS will complain it doesn't have render when you use JSX to render this component.

And this cast makes me wonder something is not right here.

Maybe some issue to the typing or I miss something.

After figuring out why, will happy to submit a PR. There are actually multiple cases needs to be clear.

The downside is you lose the CSS intellisense in VSC which is a pain...

@borisyordanov

This comment has been minimized.

Copy link

commented Dec 7, 2018

@Albert-Gao Your code isn't working for me (i copy pasted it)

I get the following errors:

[ts] Operator '>' cannot be applied to types 'boolean' and 'string'. [2365]
[ts] Type 'boolean' cannot be converted to type 'FunctionComponent<IStyledInput>'. [2352]
[ts] 'IStyledInput' only refers to a type, but is being used as a value here. [2693]

My packages:

"@types/react": "^16.7.10",
"@types/react-dom": "^16.0.11",
"@types/styled-components": "^4.1.2",
"styled-components": "^4.1.2",
"typescript": "^3.2.1"
@Dudeonyx

This comment has been minimized.

Copy link

commented Dec 8, 2018

Vscode: 1.29.1
Typescript:
image

Without types it works fine but typescript complains if i try to pass or use props

image

With types

image

IntelliSense and other features still work, but no highlighting

@Dudeonyx

This comment has been minimized.

Copy link

commented Dec 8, 2018

But this works

image

and allows me to pass the open prop.

eg. <StyledSideDrawer open={true} />

@deftomat

This comment has been minimized.

Copy link

commented Dec 9, 2018

@Dudeonyx I'm assuming that you are using VSCode.
See styled-components/vscode-styled-components#101 as it is a vscode plugin issue.

@supergoat

This comment has been minimized.

Copy link

commented Dec 12, 2018

This works for me with TypeScript 2.9.2 and Styled Components 3.3.0


interface IMyImageProps {
  border?: string,
  children?: any
}

const MediumButton = styled.button<IMyImageProps>`
  background: aliceblue;
  color: snow;
  border: 4px solid ${p  => p.border ? 'red' : 'blue'};

But lint error in TS3.1 & styled-components 4.

I used this, but as you mentioned there is a lint error with styled-components 4.

This worked for me for styled-components 4:

screenshot 2018-12-12 at 16 27 45

@microcipcip

This comment has been minimized.

Copy link

commented Jan 4, 2019

Most of the examples here make me lose the syntax highlighting on Webstorm/Phpstorm, so I've found this fix:

interface ImyComp {
  active: boolean
  small: boolean
}

const MyComp = styled.div`
  ${(p: ImyComp) => ``}; // HACK HERE!!!
  display: ${p => p.active ? 'block' : 'none'};
`
@kuuup-at-work

This comment has been minimized.

Copy link

commented Jan 25, 2019

@microcipcip
Why not in a single line?

const MyComp = styled.div`
  display: ${(p: ImyComp)  => p.active ? 'block' : 'none'};
`

This works for all following occurrences of p

@FredyC

This comment has been minimized.

Copy link

commented Jan 25, 2019

@kuuup-at-work It has a big disadvantage if you later decide to remove that property, you have to move that "hack" to some other property. For this reason, @microcipcip's workaround is kinda better.

@kuuup-at-work

This comment has been minimized.

Copy link

commented Jan 28, 2019

@FredyC
You're right but this hacky line still disturbes me.
For now I'll stay with:

const MyComp = styled.div`
    ${(p: ImyComp) => `
        // use p here ....
    `};
`;
@hegelstad

This comment has been minimized.

Copy link

commented Jan 31, 2019

What would I do when consuming styled-components that are written in JS in my typescript files? The props are not recognized..

@OzairP

This comment has been minimized.

Copy link

commented Feb 12, 2019

For intellisense inside CSS and outside for consumers I did this

@jmeyers91

This comment has been minimized.

Copy link

commented Feb 22, 2019

After spending a few hours trying to figure out why props was any in interpolated functions, I figured out that I needed this in my tsconfig.json:

"moduleResolution": "node"

I'm not sure why this fixes the issue, but maybe it will save someone else some time.

@ctrlplusb

This comment has been minimized.

Copy link

commented Mar 18, 2019

I really struggled with this for a while too, but got it working with some changes to the above solutions.

My versions:

{ 
  "@types/styled-components": "^4.1.12",
  "styled-components": "^4.1.3",
  "typescript": "^3.3.3333"
}

Custom typed helpers:

// utils/styled-components.ts
import * as styledComponents from 'styled-components';
import { ThemedStyledFunction } from 'styled-components';

import { Theme } from './theme';

const {
  default: styled,
  css,
  createGlobalStyle,
  keyframes,
  ThemeProvider,
} = styledComponents as styledComponents.ThemedStyledComponentsModule<Theme>;

function withProps<U>() {
  return <
    P extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
    T extends object,
    O extends object = {}
  >(
    fn: ThemedStyledFunction<P, T, O>,
  ): ThemedStyledFunction<P & U, T, O & U> =>
    (fn as unknown) as ThemedStyledFunction<P & U, T, O & U>;
}

export { css, createGlobalStyle, keyframes, ThemeProvider, withProps };

export default styled;

Example implementation:

// components/link.tsx
import { Link } from 'react-router-dom';

import styled, { withProps } from '../../lib/styled-components';

interface LinkStyleProps {
  inverse?: boolean;
}

export default withProps<LinkStyleProps>()(styled(Link))`
  color: ${props =>
    props.inverse
      ? props.theme.colors.primaryContrast
      : props.theme.colors.primary} !important;
`;

And an example where you don't pass the custom style props down to the underlying component:

import { Link } from 'react-router-dom';

import styled, { withProps } from '../../lib/styled-components';

interface LinkStyleProps {
  inverse?: boolean;
}

export default withProps<LinkStyleProps>()(
  styled(({ inverse, ...props }) => <Link {...props} />),
)`
  color: ${props =>
    props.inverse
      ? props.theme.colors.primaryContrast
      : props.theme.colors.primary} !important;
`;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.