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 · 100 comments
Closed

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

tal opened this issue Mar 30, 2017 · 100 comments
Labels

Comments

@tal
Copy link

tal 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
Copy link
Member

kitten 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
Copy link
Author

tal commented Mar 30, 2017 via email

@mxstbr mxstbr added the bug 🐛 label Apr 1, 2017
@mxstbr
Copy link
Member

mxstbr commented Apr 1, 2017

/cc @styled-components/typers any ideas?

@jupl
Copy link

jupl 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
Copy link

giladaya commented Apr 5, 2017

@philpl 's approach worked for me

@beshanoe
Copy link

beshanoe 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
Copy link
Contributor

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

@mxstbr
Copy link
Member

mxstbr 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 as completed Jun 30, 2017
@alloy
Copy link

alloy 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'};
`

@ghost
Copy link

ghost 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
Copy link

atfzl 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
Copy link
Contributor

Igorbek 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
Copy link
Contributor

jimthedev 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
Copy link
Contributor

jimthedev 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
Copy link

Lucasus 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
Copy link

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
Copy link

seivan 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
Copy link

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

@seivan
Copy link

seivan 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
Copy link

FourwingsY 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
Copy link

seivan 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
Copy link

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

@seivan
Copy link

seivan 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
Copy link

goldcaddy77 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

@ghost
Copy link

ghost 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
Copy link

FourwingsY 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.

@supergoat
Copy link

supergoat 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
Copy link

microcipcip 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
Copy link

@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

@danielkcz
Copy link

danielkcz 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
Copy link

@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
Copy link

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

@OzairP
Copy link

OzairP commented Feb 12, 2019

For intellisense inside CSS and outside for consumers I did this

@jmeyers91
Copy link

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
Copy link

ctrlplusb 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;
`;

@vertexlabs
Copy link

Can do simply:
border: ${(p: {YourProps?: any}) => p.YourProps ? 'red' : 'blue'};

@ni3t
Copy link

ni3t commented Jun 6, 2019

The styled wrapper can be passed in a type like so:

interface SomeInterface {
  awesome: boolean
}

const WrappedText = styled<SomeInterface>(Text)`
  color: ${({awesome}) => awesome ? "green" : "black"}
`

@CMCDragonkai
Copy link

@ni3t Is that a recent thing? When I try that with an interface type I get:

ERROR in [at-loader] ./src/components/InverterAnalysis.tsx:153:32 
    TS2344: Type 'InverterAnalysisProps' does not satisfy the constraint '"symbol" | "object" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | "canvas" | ... 155 more ... | ComponentClass<...>'.
  Type 'InverterAnalysisProps' is not assignable to type 'ComponentClass<any, any>'.
    Type 'InverterAnalysisProps' provides no match for the signature 'new (props: any, context?: any): Component<any, any, any>'.

@yasso1am
Copy link

Can confirm that @ni3t 's recommendation worked just fine for me.

@hypeofpipe
Copy link

Thanks @ni3t 🎈

@joshuatshaffer
Copy link

joshuatshaffer commented Jul 5, 2019

Thanks @ni3t. That helped a lot.

@CMCDragonkai I think you tried something like styled<MyExtraProps>.button to get that error. You need to use styled.button<MyExtraProps>.

To summarize:

// Some properties that we use in our styles, 
// but are not part of the underlying component's props.
type MyExtraProps = {
    anExtraProp: WhateverType
};

// For basic HTML elements:
const MyStyledComponent = styled.button<MyExtraProps>`
    ... ${props => props.anExtraProp } ...
`;

// For extending components that support the className prop:
const MyStyledComponent = styled<MyExtraProps>(AStyleableComponent)`
    ... ${props => props.anExtraProp } ...
`;

(P.S. The versions in my package.json are styled-components@^4.2.0, @types/styled-components@^4.1.16, and typescript@^3.3.4000.)

@paulmelnikow
Copy link

Based on the above comment and my experience using styled-components with React Native, it seems that styled<SomeInterface>(Text) in @ni3t's answer should be styled.Text<SomeInterface>.

@ni3t
Copy link

ni3t commented Jul 25, 2019

Ah I've never tried it using the built-in styled.Whatever.

Thanks for the feedback!

@openkalki
Copy link

openkalki commented Aug 28, 2019

I came across this issue today and found something that worked for my use-case.

I wanted to:

  1. Define my own props that I could pass into my React Functional Component
  2. Use "React.FC" interface so that my functional component uses the correct interface
  3. Use styled-components to change a style based on a prop that I pass down

Here is how I solved it:

import React from "react";
import styled from "styled-components";

interface LayoutProps {
  padding?: string;
}

const LayoutContainer = styled.div<LayoutProps>`
  display: flex;
  padding: ${props =>
    props.padding !== "undefined" ? props.padding : "0 20px"};
  align-items: center;
  justify-content: center;
  flex: 1;
  flex-direction: column;
`;

const Layout: React.FC<LayoutProps> = props => {
  return <LayoutContainer {...props}>{props.children}</LayoutContainer>;
};

export default Layout;

Hope this helps people

@noelrappin
Copy link

So, I had this:

const Foo = styled.span.attrs(props => ({ className: "button" }))`
`

Which was fine, but when I tried to add a prop type:

const Foo = styled.span<IFooProps>.attrs(props => ({ className: "button" }))`
`

Then I get a TypeScript error, because it doesn't recognize attrs anymore.

Is there a workaround here I'm missing?

@joshuatshaffer
Copy link

@noelrappin Try this:

const Foo = styled.span.attrs<IFooProps>(props => ({ className: "button" }))`
`

What's happening is that the <IFooProps> is you passing a type argument to a generic function.
See https://www.typescriptlang.org/docs/handbook/generics.html

@glenwinters
Copy link

For those that find this issue via google, here's the answer in the documentation:
https://www.styled-components.com/docs/api#using-custom-props

Example:

interface TitleProps {
  readonly isActive: boolean;
};

const Title = styled.h1<TitleProps>`
  color: ${props => props.isActive ? props.theme.colors.main : props.theme.colors.secondary};
`

@mayowDev
Copy link

mayowDev commented Nov 8, 2019

i was solving button styled component and this one worked for me

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

const ButtonContainer: StyledComponent<"button", any, {}, never>= styled.button your code here

@Kelbie

This comment has been minimized.

@paulmelnikow

This comment has been minimized.

@styled-components styled-components locked as resolved and limited conversation to collaborators Feb 13, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests