2
votes

I found myself in a current situation, I have my Print components which are basic building blocks of my design system and set css to some normalised styles, here is example of input

InputPrint.tsx

import styled from 'styled-components';
import theme from '../util/theme';

/**
 * Styles
 */
const InputPrint = styled.input`
  display: inline-block;
  appearance: none;
`;

export default InputPrint;

I then use this Print in my actual component(s)

Input.tsx

import React from 'react';
import styled from 'styled-components';
import InputPrint from '../blueprints/InputPrint';

/**
 * Styles
 */
const StyledInput = styled(InputPrint)`
  width: 65vw;
  color: #797155;
`;

/**
 * Component
 */
function Input({ ...props }) {
  return (
    <StyledInput
      autoComplete="off"
      autoCorrect="off"
      autoCapitalize="off"
      spellCheck={false}
      {...props}
    />
  );
}

export default Input;

And issue here happens with props, in some components I might have extra props or overwrite default ones as above, but still want to pass them all valid <input /> element props. I tried 2 approaches at typing them i.e.

props: React.ComponentProps<typeof InputPrint>

If I do it this way ^ I don't get any autocomplete on props when I use my <Input />

props: React.HTMLProps<HTMLInputElement>

if I do it this way ^ I get a typescript error below highlighted inside Input.tsx for <StyledInput />

const StyledInput: StyledComponent<"input", any, {}, never> Styles

No overload matches this call. Overload 1 of 2, '(props: Pick, HTMLInputElement>, "form" | ... 283 more ... | "step"> & { ...; }, "ref" | ... 284 more ... | "step"> & Partial<...>, "ref" | ... 284 more ... | "step"> & { ...; } & { ...; }): ReactElement<...>', gave the following error. Type '{ accept?: string | undefined; acceptCharset?: string | undefined; action?: string | undefined; allowFullScreen?: boolean | undefined; allowTransparency?: boolean | undefined; alt?: string | undefined; ... 353 more ...; key?: string | ... 1 more ... | undefined; }' is not assignable to type 'Pick, HTMLInputElement>, "form" | ... 283 more ... | "step"> & { ...; }, "ref" | ... 284 more ... | "step"> & Partial<...>, "ref" | ... 284 more ... | "step">'. Types of property 'ref' are incompatible. Type 'string | ((instance: HTMLInputElement | null) => void) | RefObject | null | undefined' is not assignable to type '((instance: HTMLInputElement | null) => void) | RefObject | null | undefined'. Type 'string' is not assignable to type '((instance: HTMLInputElement | null) => void) | RefObject | null | undefined'. Overload 2 of 2, '(props: StyledComponentPropsWithAs<"symbol" | "object" | ComponentClass | FunctionComponent | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | ... 164 more ... | "view", any, {}, never>): ReactElement<...>', gave the following error. Type '{ accept?: string | undefined; acceptCharset?: string | undefined; action?: string | undefined; allowFullScreen?: boolean | undefined; allowTransparency?: boolean | undefined; alt?: string | undefined; ... 353 more ...; key?: string | ... 1 more ... | undefined; }' is not assignable to type '(IntrinsicAttributes & Pick & Partial>, string | number | symbol> & { ...; } & { ...; }) | (IntrinsicAttributes & ... 3 more ... & { ...; })'. Type '{ accept?: string | undefined; acceptCharset?: string | undefined; action?: string | undefined; allowFullScreen?: boolean | undefined; allowTransparency?: boolean | undefined; alt?: string | undefined; ... 353 more ...; key?: string | ... 1 more ... | undefined; }' is not assignable to type '{ as?: "symbol" | "object" | ComponentClass | FunctionComponent | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | ... 165 more ... | undefined; }'. Types of property 'as' are incompatible. Type 'string | undefined' is not assignable to type '"symbol" | "object" | ComponentClass | FunctionComponent | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | ... 165 more ... | undefined'. Type 'string' is not assignable to type '"symbol" | "object" | ComponentClass | FunctionComponent | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | ... 165 more ... | undefined'.ts(2769)

2
Did you try using <typeof InputPrint & HTMLInputElement> as props?voiys
Using both of them that way won't satisfy neither ComponentProps and HTMLProps and I think using both of these as well will result in similar errors?Ilja

2 Answers

4
votes

I export default styled components like this:

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

interface Props extends React.ComponentPropsWithoutRef<'input'> {
  err?: boolean
  maxWidth?: string
}

const Input = forwardRef<HTMLInputElement, Props>((props, ref) => {
  return <StyledInput ref={ref} {...props} />
})


const StyledInput = styled.input<Props>`
  margin: 5px;
  background-color: ${({ theme, type }): string => (type === 'color' ? 'transparent' : theme.secondaryColor)};
  color: ${({ theme }): string => theme.textColor};
  max-width: calc(${({ maxWidth }): string => maxWidth || '100%'} - ${defPadding * 2}px);
  width: 100%;
  text-align: center;
`

Input.displayName = 'Input'

export { Input }

And then just use it or override it's default styles as i wish

import React from 'react'
import styled from 'styled-components'
import {Input} from '@frontend'

    export default function App() {
      return (
         <Input type="submit" value="Submit" err={true} />
         <RestyledRedInput type="submit" value="Submit" />
      )
    }

    // you can restyle it because of forward ref
    const RestyledRedInput = styled(Input)`
      background-color: red;
    `

For theming i recommend you to use context:

import React, { useState, useEffect } from 'react'
import { clone } from 'global'
import { defaultGeneralTheme } from '../data/defaultGeneralTheme'
import { ThemeProvider, createGlobalStyle } from 'styled-components'

export const ThemeContext = React.createContext(null)
export const ThemeContextProvider = props => {
  const [generalTheme, setGeneralTheme] = useState(clone(defaultGeneralTheme))
  const [theme, setTheme] = useState(currentTheme())
  const [isDay, setIsDay] = useState(isItDay())

  useEffect(() => {
    setTheme(currentTheme())
    setIsDay(isItDay())
  }, [generalTheme])

  function currentTheme() {
    return generalTheme.isDay ? generalTheme.day : generalTheme.night
  }

  function isItDay() {
    return generalTheme.isDay ? true : false
  }

  return (
    <ThemeContext.Provider value={{ theme, generalTheme, setGeneralTheme, isDay }}>
      <ThemeProvider theme={theme}>
        <>
          <GlobalStyle />
          {props.children}
        </>
      </ThemeProvider>
    </ThemeContext.Provider>
  )
}

const GlobalStyle = createGlobalStyle`
  /* Global */
  html {
    word-break: break-word;
  }
  body {  
    line-height: 1.2rem;
    background-color: ${({ theme }) => theme.secondaryColor};
  }
`
0
votes

The question is a bit old. But it might help other people who are facing the same issue. If you are using Styled components, then the use of transient props could help in this case.

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.

const Comp = styled.div`
  color: ${props =>
    props.$draggable || 'black'};
`;

render(
  <Comp $draggable="red" draggable="true">
    Drag me!
  </Comp>
);