Giter VIP home page Giter VIP logo

Comments (14)

bozdoz avatar bozdoz commented on March 29, 2024 3

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" />

from react.

ferdaber avatar ferdaber commented on March 29, 2024 1

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.

from react.

ferdaber avatar ferdaber commented on March 29, 2024

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)

from react.

swyxio avatar swyxio commented on March 29, 2024

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

from react.

ferdaber avatar ferdaber commented on March 29, 2024

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

from react.

infctr avatar infctr commented on March 29, 2024

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!

from react.

infctr avatar infctr commented on March 29, 2024

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
}

from react.

ferdaber avatar ferdaber commented on March 29, 2024

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)
}

from react.

infctr avatar infctr commented on March 29, 2024

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)

from react.

infctr avatar infctr commented on March 29, 2024

@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

from react.

stale avatar stale commented on March 29, 2024

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!

from react.

stale avatar stale commented on March 29, 2024

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!

from react.

swyxio avatar swyxio commented on March 29, 2024

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

from react.

alexeyr-ci avatar alexeyr-ci commented on March 29, 2024

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

from react.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.