Using styled-components with props and TypeScript
Asked Answered
P

9

111

I'm trying to integrate TypeScript into our project and so far I stumbled upon one issue with styled-components library.

Consider this component

import * as React from "react";
import styled from "styled-components/native";
import { TouchableOpacity } from "react-native";

// -- types ----------------------------------------------------------------- //
export interface Props {
  onPress: any;
  src: any;
  width: string;
  height: string;
}

// -- styling --------------------------------------------------------------- //
const Icon = styled.Image`
  width: ${(p: Props) => p.width};
  height: ${(p: Props) => p.height};
`;

class TouchableIcon extends React.Component<Props> {
  // -- default props ------------------------------------------------------- //
  static defaultProps: Partial<Props> = {
    src: null,
    width: "20px",
    height: "20px"
  };

  // -- render -------------------------------------------------------------- //
  render() {
    const { onPress, src, width, height } = this.props;
    return (
      <TouchableOpacity onPress={onPress}>
        <Icon source={src} width={width} height={height} />
      </TouchableOpacity>
    );
  }
}

export default TouchableIcon;

Following line throws 3 errors, that are same in nature <Icon source={src} width={width} height={height} />

Type {source: any; width: string; height: string;} is not assignable to type IntrinsicAttributes ... Property 'onPress' is missing in type {source: any; width: string; height: string;}

Not entirely sure what this is and how to fix it, do I somehow need to declare these on Icon or something of this sort?

EDIT: typescript v2.6.1, styled-components v2.2.3

Pannell answered 2/11, 2017 at 14:2 Comment(0)
F
19

This answer is outdated, the most current answer is here: https://mcmap.net/q/194390/-using-styled-components-with-props-and-typescript

As far as I can tell there is no official way (yet?) to do this, but you can solve it with a bit of trickery. First, create a withProps.ts file with the following content:

import * as React from 'react'
import { ThemedStyledFunction } from 'styled-components'

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

export { withProps }

Now, inside your .tsx files, use it like this:

// ... your other imports
import { withProps } from './withProps'

export interface IconProps {
  onPress: any;
  src: any;
  width: string;
  height: string;
}

const Icon = withProps<IconProps>()(styled.Image)`
  width: ${(p: IconProps) => p.width};
  height: ${(p: IconProps) => p.height};
`;

And you should be good to go. It's definitely not ideal and hopefully there will be a way to provide generics to template literals soon in TS, but I guess that for now this is your best option.

Credit is given where credit is due: I copypasted this from here

Filomena answered 10/11, 2017 at 11:43 Comment(1)
This was a great solution, but @elnygren's comment below is definitely the most elegant current solution.Roping
C
206

There have been some recent developments and with a new version of Typescript (eg. 3.0.1) and styled-components (eg. 3.4.5) there's no need for a separate helper. You can specify the interface/type of your props to styled-components directly.

interface Props {
  onPress: any;
  src: any;
  width: string;
  height: string;
}

const Icon = styled.Image<Props>`
  width: ${p => p.width};
  height: ${p => p.height};
`;

and if you want to be more precise and ignore the onPress

const Icon = styled.Image<Pick<Props, 'src' | 'width' | 'height'>>`
  width: ${p => p.width};
  height: ${p => p.height};
`;
Central answered 27/8, 2018 at 19:18 Comment(5)
Neat! Works well with themes too. This should be the selected answer. Only problem is it breaks my syntax highlighting...Mistiemistime
Shouldn't it be styled.image<Props>?Slinkman
@ManuRauck I don't agree. Type {source: any; width: string; height: string;} is not assignable to type IntrinsicAttributes ... Property 'onPress' is missing in type {source: any; width: string; height: string;} onPress is clearly missing, so onPress is a prop of ImageSlinkman
Can anyone recommend the solution to make Typescript accept keyframes with params?Drilling
Can use styled.Image<Omit<Props, 'onPress'>>` which is a lil shorterColonize
A
22

The easiest way as styled-components docs said:

import styled from 'styled-components';
import Header from './Header';

const NewHeader = styled(Header)<{ customColor: string }>`
  color: ${(props) => props.customColor};
`;
// Header will also receive props.customColor
Adlei answered 31/3, 2022 at 12:58 Comment(0)
F
19

This answer is outdated, the most current answer is here: https://mcmap.net/q/194390/-using-styled-components-with-props-and-typescript

As far as I can tell there is no official way (yet?) to do this, but you can solve it with a bit of trickery. First, create a withProps.ts file with the following content:

import * as React from 'react'
import { ThemedStyledFunction } from 'styled-components'

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

export { withProps }

Now, inside your .tsx files, use it like this:

// ... your other imports
import { withProps } from './withProps'

export interface IconProps {
  onPress: any;
  src: any;
  width: string;
  height: string;
}

const Icon = withProps<IconProps>()(styled.Image)`
  width: ${(p: IconProps) => p.width};
  height: ${(p: IconProps) => p.height};
`;

And you should be good to go. It's definitely not ideal and hopefully there will be a way to provide generics to template literals soon in TS, but I guess that for now this is your best option.

Credit is given where credit is due: I copypasted this from here

Filomena answered 10/11, 2017 at 11:43 Comment(1)
This was a great solution, but @elnygren's comment below is definitely the most elegant current solution.Roping
C
13

styled-component

    import styled from 'styled-components';

interface Props {
    height: number;
}

export const Wrapper = styled.div<Props>`
    padding: 5%;
    height: ${(props) => props.height}%;
`;

index

import React, { FunctionComponent } from 'react';
import { Wrapper } from './Wrapper';

interface Props {
    className?: string;
    title: string;
    height: number;
}

export const MainBoardList: FunctionComponent<Props> = ({ className, title, height }) => (
    <Wrapper height={height} className={className}>
        {title}
    </Wrapper>
);
    

should work

Cornflower answered 4/4, 2021 at 15:12 Comment(0)
M
7

An example using ColorCard with color prop

import styled from 'styled-components';

export const ColorCard = styled.div<{ color: string }>`
  background-color: ${({ color }) => color};
`;
Modla answered 25/7, 2021 at 20:14 Comment(2)
it is amazing, thanksUnblessed
This should be accepted answerRase
D
6

You need only specify a interface:

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

interface PropsGlobalStyle {
  dark: boolean
}

export default createGlobalStyle`
  ${({ dark }: PropsGlobalStyled) => css`
    body {
      box-sizing: border-box;
      margin: 0;
      font-family: Arial, Helvetica, sans-serif;
      color: ${dark ? '#fff' : '#000'};
      background-color: ${dark ? '#000' : '#fff'};
    }
  `};
`;
Declaratory answered 2/8, 2020 at 2:38 Comment(0)
O
3

The answer from @elnygren worked for me. Just one question from me. How to assign a default value to the following code (copied from the answer of @elnygren). e.g. If I do not want to pass any value to the 'width' and 'height' a default value will be used.

const Icon = styled.Image<Pick<Props, 'src' | 'width' | 'height'>>`
  width: ${p => p.width};
  height: ${p => p.height};
`;
Ovariectomy answered 12/10, 2021 at 0:38 Comment(1)
If you have a new question, please ask it by clicking the Ask Question button. Include a link to this question if it helps provide context. - From ReviewSheff
P
1

I'm struggling through this myself, but I think the problem is that you are using the Props interface inside the styled component. Try creating another interface with just the image props and use that in your styled component:

import * as React from "react";
import styled from "styled-components/native";
import { TouchableOpacity } from "react-native";

// -- types ----------------------------------------------------------------- //
export interface Props {
  onPress: any;
  src: any;
  width: string;
  height: string;
}


export interface ImageProps {
  src: string;
  width: string;
  height: string;
}

// -- styling --------------------------------------------------------------- //
const Icon = styled.Image`
  width: ${(p: ImageProps ) => p.width};
  height: ${(p: ImageProps ) => p.height};
`;

class TouchableIcon extends React.Component<Props> {
  // -- default props ------------------------------------------------------- //
  static defaultProps: Partial<Props> = {
    src: null,
    width: "20px",
    height: "20px"
  };

  // -- render -------------------------------------------------------------- //
  render() {
    const { onPress, src, width, height } = this.props;
    return (
      <TouchableOpacity onPress={onPress}>
        <Icon source={src} width={width} height={height} />
      </TouchableOpacity>
    );
  }
}

export default TouchableIcon;

Seems to work but I hate to have to duplicate those interfaces. Hopefully someone else can show the correct way or maybe embedding the ImageProps into Props?

Probate answered 2/11, 2017 at 14:36 Comment(3)
This didn't work for me, but tmp workaround is const Icon: any = styled.Image I have no idea why it works, howeverPannell
Glad you got it to work, that worked for me as well. I am in react-dom so maybe there is some difference there.Probate
that's what I needed: width: ${(p: ImageProps ) => p.width};Lefevre
M
0

The docs state:

If you want to prevent props meant to be consumed by styled components from being passed to the underlying React node or rendered to the DOM element, you can prefix the prop name with a dollar sign ($), turning it into a transient prop.

Mountainous answered 4/12, 2023 at 20:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.