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

Hoc example that supports default props of wrapped component #86

Closed
ferdaber opened this issue Feb 8, 2019 · 14 comments
Closed

Hoc example that supports default props of wrapped component #86

ferdaber opened this issue Feb 8, 2019 · 14 comments
Labels
ADVANCED Advanced Cheatsheet HOC HOC Cheatsheet wontfix This will not be worked on

Comments

@ferdaber
Copy link
Collaborator

ferdaber commented Feb 8, 2019

Cc @brieb

@ferdaber
Copy link
Collaborator Author

ferdaber commented Feb 9, 2019

working example:

import React from 'react'

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>

export interface WithThemeProps {
  theme: {}
}

interface AddlProps {
  foo: string
}

function withTheme<C extends React.JSXElementConstructor<React.ComponentProps<C> & WithThemeProps>>(
  Component: C
) {
  type OuterProps = JSX.LibraryManagedAttributes<
    C,
    Omit<React.ComponentProps<C>, keyof WithThemeProps>
  > &
    AddlProps

  class WithTheme extends React.Component<OuterProps> {
    render() {
      return (
        <Component
          {...this.props as JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>}
          // this is bad below because `theme` is not typechecked against inner component
          theme={1}
        />
      )
    }
  }
  return WithTheme
}

function withTheme<C extends React.JSXElementConstructor<React.ComponentProps<C> & WithThemeProps>>(
  Component: C
) {
  type OuterProps = JSX.LibraryManagedAttributes<C, Omit<React.ComponentProps<C>, 'theme'>> &
    AddlProps

  class WithTheme extends React.Component<OuterProps> {
    render() {
      const themeProps: WithThemeProps = {
        theme: '',
      }
      return <Component {...this.props as any} {...themeProps} />
    }
  }
  return WithTheme
}

interface MyComponentProps extends WithThemeProps {
  message: string
}

class MyComponent extends React.Component<MyComponentProps> {
  static defaultProps = {
    message: '',
  }

  render() {
    return <div />
  }
}

interface MyBadComponentProps {
  message: string
}

class MyBadComponent extends React.Component<MyBadComponentProps> {
  static defaultProps = {
    message: '',
  }

  render() {
    return <div />
  }
}

const Test = withTheme(MyComponent)
const testEl = <Test />

const FailTest = withTheme(MyBadComponent)

@swyxio
Copy link
Collaborator

swyxio commented Feb 11, 2019

love it. will do some writing up some time this week. may need a whole cheatsheet just for HOCs, ha.

@ferdaber
Copy link
Collaborator Author

Related issue opened in TS repo: microsoft/TypeScript#29873

@infctr
Copy link

infctr commented Feb 18, 2019

cleaner?

function withTheme<
  C extends React.ComponentType<React.ComponentProps<C> & WithThemeProps>,
  ResolvedProps = JSX.LibraryManagedAttributes<C, Omit<React.ComponentProps<C>, keyof WithThemeProps>>
>(
  Component: C
) {
  return class WithTheme extends React.Component<AddlProps & ResolvedProps> {
    render() {
      const themeProps: WithThemeProps = {
        theme: '',
      }

      return (
        <Component 
          {...this.props as JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>} 
          {...themeProps} 
        />
      );
    }
  }
}

@ferdaber thanks for the valid typecast hint!

@infctr
Copy link

infctr commented Feb 18, 2019

How about using with react-redux' connect? Typechecking here gets tricky

function withTheme<
  C extends React.ComponentType<React.ComponentProps<C> & WithThemeProps>,
  ResolvedProps = JSX.LibraryManagedAttributes<C, Omit<React.ComponentProps<C>, keyof WithThemeProps>>
>(
  Component: C
) {
  class WithTheme extends React.Component<WithThemeProps & AddlProps & ResolvedProps> {
    render() {
      const { theme, ...props } = this.props;

      return (
        <Component 
          {...props as JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>} 
          theme={theme}
        />
      );
    }
  }

  return connect<WithThemeProps, void, AddlProps & ResolvedProps>(
    () => ({ theme: 'theme' })
  )(WithTheme) // error, types don't match
}

@ferdaber
Copy link
Collaborator Author

If you're just wrapping something with a connect call you should just simplify it:

function withTheme<P extends WithThemeProps>(component: React.ComponentType<P>) {
  return connect(() => ({ theme: 'theme' }))(component)
}

@infctr
Copy link

infctr commented Feb 18, 2019

I'm getting same error tho

Argument of type 'ComponentType<P>' is not assignable to parameter of type 'ComponentType<Matching<WithThemeProps & DispatchProp<AnyAction>, P>>'.
  Type 'ComponentClass<P, any>' is not assignable to type 'ComponentType<Matching<WithThemeProps & DispatchProp<AnyAction>, P>>'.
    Type 'ComponentClass<P, any>' is not assignable to type 'ComponentClass<Matching<WithThemeProps & DispatchProp<AnyAction>, P>, any>'.
      Type 'P' is not assignable to type 'Matching<WithThemeProps & DispatchProp<AnyAction>, P>'.
        Type 'WithThemeProps' is not assignable to type 'Matching<WithThemeProps & DispatchProp<AnyAction>, P>'.ts(2345)

@infctr
Copy link

infctr commented Feb 18, 2019

@ferdaber And besides this loses inner component's default props

import React from 'react'
import { compose } from 'redux'
import { connect } from 'react-redux'

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>

// wrapper 
export interface WithThemeProps {
  theme: {}
}

interface AddlProps {
  foo: string
}

function withTheme<P extends WithThemeProps>(component: React.ComponentType<P>) {
  return connect<WithThemeProps, void, AddlProps & Omit<P, keyof WithThemeProps>>(() => ({ theme: 'theme' }))(component as React.ComponentType)
}

// component
interface MyComponentProps extends WithThemeProps {
  message: string
  required: string
}

class MyComponent extends React.Component<MyComponentProps> {
  static defaultProps = {
    message: '',
  }

  render() {
    return <div />
  }
}

const Test = withTheme(MyComponent)
const testEl = <Test required="123" foo='123' /> // error, message is missing

Another issue, not sure how it's related, but I've ran into this playing with HOC's and default props

// use with `compose` from redux
const Composed = compose(withTheme)(MyComponent)

const x = <Composed required="123" ></Composed> // error, all component's props are lost

@ferdaber
Copy link
Collaborator Author

I can play around with it, react-redux's types rely heavily on conditional types which don't get inferred very well, I'll take a look.

@stale
Copy link

stale bot commented Aug 20, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions!

@stale stale bot added the wontfix This will not be worked on label Aug 20, 2020
@swyxio swyxio added ADVANCED Advanced Cheatsheet HOC HOC Cheatsheet and removed wontfix This will not be worked on labels Aug 22, 2020
@stale
Copy link

stale bot commented Oct 21, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions!

@stale stale bot added the wontfix This will not be worked on label Oct 21, 2020
@swyxio swyxio closed this as completed in f7dbf8f Oct 21, 2020
@bozdoz
Copy link
Contributor

bozdoz commented Apr 20, 2021

I think I finally got a working version of this:

TS Playground

I used @infctr 's example:

import React from 'react';

export interface WithThemeProps {
  theme: string
}

function withTheme<
  C extends React.ComponentType<React.ComponentProps<C> & WithThemeProps>,
  ResolvedProps = JSX.LibraryManagedAttributes<C, Omit<React.ComponentProps<C>, keyof WithThemeProps>>
>(
  Component: C
) {
  return class WithTheme extends React.Component<ResolvedProps> {
    render() {
      const themeProps: WithThemeProps = {
        theme: '',
      }

      return (
        <Component 
          {...this.props as JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>} 
          {...themeProps} 
        />
      );
    }
  }
}

class Button extends React.Component<{size: string, color: string} & WithThemeProps> {
    static defaultProps = {
        size: 'm'
    }
}

const ThemedButton = withTheme(Button);

// Does not work: missing theme
const a = () => <Button color="red" />
// Works: theme provided by HOC
const b = () => <ThemedButton color="red" />

@swyxio
Copy link
Collaborator

swyxio commented Apr 23, 2021

nice work @bozdoz - if you spot a way to add it back to the main docs to help others, please be my guest, this is a shared thing

bernssolg added a commit to bernssolg/My-React-Sample that referenced this issue Feb 28, 2022
erinodev added a commit to erinodev/My-React-project that referenced this issue Feb 28, 2022
petardev101 added a commit to petardev101/react that referenced this issue Jun 4, 2022
supercrytoking added a commit to supercrytoking/react that referenced this issue Jul 14, 2022
kevindavies8 added a commit to kevindavies8/react-full-stack-developer that referenced this issue Aug 24, 2022
johnfrench3 pushed a commit to johnfrench3/react-Fronted-developer that referenced this issue Sep 7, 2022
ericbrown2716 added a commit to ericbrown2716/react-stack-build-website that referenced this issue Sep 29, 2022
peterjohnson4987 added a commit to peterjohnson4987/full-stack-developer-react that referenced this issue Oct 3, 2022
renawolford6 pushed a commit to renawolford6/react-husky-website that referenced this issue Oct 6, 2022
Yoshidayoshi23 added a commit to Yoshidayoshi23/react that referenced this issue Oct 20, 2022
renawolford6 added a commit to renawolford6/react-dev-build-doc- that referenced this issue Nov 10, 2022
coopfeathy added a commit to coopfeathy/cheatsheet that referenced this issue Dec 4, 2022
dreamcoder75 added a commit to dreamcoder75/react-sample that referenced this issue Jan 15, 2023
blackphantom0221 added a commit to blackphantom0221/typescript-cheatsheets-react that referenced this issue Jan 23, 2023
tamatashi pushed a commit to tamatashi/chart that referenced this issue Feb 27, 2023
spartacus0816 pushed a commit to spartacus0816/react_frontend_master that referenced this issue May 20, 2023
AIDevMonster added a commit to AIDevMonster/Awesome-React that referenced this issue Jun 21, 2023
whiteghostDev added a commit to whiteghostDev/Awesome-React that referenced this issue Aug 6, 2023
cedev935 added a commit to cedev935/React-TypeScript that referenced this issue Sep 11, 2023
@alexeyr-ci
Copy link

@bozdoz The TypeScript playground example doesn't seem to compile anymore.

aleksandaralek added a commit to aleksandaralek/typescript-react-cheatsheet that referenced this issue Oct 24, 2023
xbucks added a commit to xbucks/react-cheatsheets that referenced this issue Oct 24, 2023
joyfulmagician added a commit to joyfulmagician/react that referenced this issue Oct 25, 2023
le-pus pushed a commit to le-pus/React-Typescript that referenced this issue Oct 26, 2023
atsumi000105 added a commit to atsumi000105/typescript-cheatsheets-react that referenced this issue Dec 8, 2023
secretsuperstar1109 added a commit to secretsuperstar1109/react-typescript-cheatsheets that referenced this issue Dec 9, 2023
champion119 added a commit to champion119/react that referenced this issue Jan 5, 2024
SManOlaf38 pushed a commit to SManOlaf38/react_typescript that referenced this issue Feb 20, 2024
SManOlaf38 added a commit to SManOlaf38/react_typescript that referenced this issue Feb 20, 2024
rising-dragon360 added a commit to rising-dragon360/react that referenced this issue Mar 13, 2024
EugeneYoona added a commit to EugeneYoona/React_full_src that referenced this issue Apr 10, 2024
snowMan128 added a commit to snowMan128/TypeReact that referenced this issue Apr 15, 2024
fairskyDev0201 added a commit to fairskyDev0201/typescript-cheatsheet that referenced this issue Apr 17, 2024
bluesky4293 added a commit to bluesky4293/React that referenced this issue Apr 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ADVANCED Advanced Cheatsheet HOC HOC Cheatsheet wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

5 participants