question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Typescript] Question: Generic type arguments in JSX elements working with withStyles

See original GitHub issue
  • This is a v1.x issue (v0.x is no longer maintained).
  • I have searched the issues of this repository and believe that this is not a duplicate.

I wanted to leverage the generic type argument in JSX elements feature introduced in ts 2.9.1. However, when it comes to exporting with withStyles, I don’t know how to expose that generic type parameter to the outside world.

class MyComponent<T> extends React.PureComponent<MyComponentProps<T>> {...}

export default withStyles(styles)(MyComponent);

However, after doing this, when I call MyComponent like below, it said that MyComponent expects 0 type arguments, but provided 1. It seems like {} was passed to MyComponent as the default type parameter if none is specified.

<MyComponent<string> prop1={...} /> // error: expected 0 type arguments, but provided 1.

So, my question is how I can achieve generic type arguments in JSX element with HOC?

Your Environment

Tech Version
Material-UI v1.2.2
React 16.4.1
browser
etc

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:4
  • Comments:9 (4 by maintainers)

github_iconTop GitHub Comments

4reactions
mattmccutchencommented, Sep 30, 2018

Edit: I am now recommending the following solution based on franklixuefei’s solution. It doesn’t require a change to Material UI. See the additional remarks on Stack Overflow.

class WrappedBaseFormCard<T> extends React.Component<
  // Or `PropsOf<WrappedBaseFormCard<T>["C"]>` from @material-ui/core if you don't mind the dependency.
  WrappedBaseFormCard<T>["C"] extends React.ComponentType<infer P> ? P : never,
  {}> {
  private readonly C = withStyles(styles)(
    // JSX.LibraryManagedAttributes handles defaultProps, etc.  If you don't
    // need that, you can use `BaseFormCard<T>["props"]` or hard-code the props type.
    (props: JSX.LibraryManagedAttributes<typeof BaseFormCard, BaseFormCard<T>["props"]>) =>
      <BaseFormCard<T> {...props} />);
  render() {
    return <this.C {...this.props} />;
  }
}
3reactions
franklixuefeicommented, Jun 26, 2018

@jasanst Take a look at a complete example below.

Note that this is the real code from my project. No modification made yet.

import Fade from '@material-ui/core/Fade';
import Paper from '@material-ui/core/Paper';
import { Theme } from '@material-ui/core/styles/createMuiTheme';
import withStyles, { CSSProperties, WithStyles } from '@material-ui/core/styles/withStyles';
import Typography from '@material-ui/core/Typography';
import { ControllerStateAndHelpers } from 'downshift';
import * as React from 'react';
import AutocompleteMenuItem from './AutocompleteMenuItem';

const styles = ({ palette, spacing, zIndex }: Theme) => ({
  menu: {
    position: 'relative',
    zIndex: zIndex.drawer + 1
  } as CSSProperties,
  paper: {
    position: 'absolute',
    zIndex: zIndex.modal,
    width: '100%',
    maxHeight: 400,
    overflow: 'auto',
  } as CSSProperties,
  listContainer: {
    position: 'relative',
    overflowY: 'auto',
    backgroundColor: 'inherit',
  } as CSSProperties,
  noMatch: {
    padding: '8px 16px 8px 24px',
    fontStyle: 'italic',
    color: palette.text.disabled
  } as CSSProperties,
});

export interface IAutocompleteMenuProps<TItem> {
  downshiftControllerStateAndHelpers: ControllerStateAndHelpers<TItem>;
  items: TItem[];
  selectedItems: TItem[];
  noMatchText: string;
  loading: boolean;
  loadingText: string;
}

// TODO: if we want to enable async menu content loading, then we need to execute
// clearItems() on data retrieval.
// https://github.com/mui-org/@material-ui/core/issues/10657
// https://codesandbox.io/s/github/kentcdodds/advanced-downshift

class AutocompleteMenu<TItem> extends React.PureComponent<IAutocompleteMenuProps<TItem> & WithStyles<typeof styles>> {
  constructor(props: IAutocompleteMenuProps<TItem> & WithStyles<typeof styles>) {
    super(props);
    this.renderMenuItems = this.renderMenuItems.bind(this);
    this.renderMenu = this.renderMenu.bind(this);
  }

  render() {
    const { downshiftControllerStateAndHelpers, classes } = this.props;
    const { isOpen, getMenuProps } = downshiftControllerStateAndHelpers;
    return (
      <Fade in={isOpen} mountOnEnter unmountOnExit>
        <div 
          className={classes.menu}
          {...getMenuProps({
            'aria-label': 'autocompletion menu'
          })}
        >
          <Paper classes={{ root: classes.paper }}>
            <div className={classes.listContainer}>
              {this.renderMenu()}
            </div>
          </Paper>
        </div>
      </Fade>
    );
  }

  private renderMenuItems(items: TItem[]) {
    const { downshiftControllerStateAndHelpers, selectedItems } = this.props;
    const { highlightedIndex, itemToString } = downshiftControllerStateAndHelpers;
    return items.map((item, index) => {
      return (
        <AutocompleteMenuItem<TItem>
          index={index}
          highlighted={index === highlightedIndex}
          selected={selectedItems.some(
            (selectedItem) => itemToString(selectedItem).toLowerCase() === itemToString(item).toLowerCase())}
          downshiftControllerStateAndHelpers={downshiftControllerStateAndHelpers}
          item={item}
          key={index}
        />
      );
    });
  }

  private renderMenu() {
    const { classes, noMatchText, items } = this.props;
    if (items.length === 0) {
      return (
        <div className={classes.noMatch}>
          <Typography color={'inherit'} noWrap>{noMatchText}</Typography>
        </div>
      );
    }
    return (
      <>
        {this.renderMenuItems(items)}
      </>
    );
  }
}

// tslint:disable-next-line:max-classes-per-file
export default class<T> extends React.PureComponent<IAutocompleteMenuProps<T>> {
  
  private readonly C = this.wrapperFunc();
  
  render() {
    return <this.C {...this.props} />;
  }

  private wrapperFunc() {
    type t = new() => AutocompleteMenu<T>;
    return withStyles(styles)(AutocompleteMenu as t);
  }
}

Usage:

<AutocompleteMenu<TItem>
  downshiftControllerStateAndHelpers={stateAndHelpers}
  noMatchText={noMatchText || 'No results found'}
  items={filteredCandidates}
  loading={loading || false}
  loadingText={loadingText || 'Loading...'}
  selectedItems={selectedItem ? [selectedItem] : []}
/>
Read more comments on GitHub >

github_iconTop Results From Across the Web

[Typescript] Question: Generic type arguments in JSX ... - GitHub
I wanted to leverage the generic type argument in JSX elements ... Generic type arguments in JSX elements working with withStyles #11921.
Read more >
Generic type arguments in JSX elements with withStyles
It's jaw-dropping that referencing a property type from a type argument in the extends clause works, but it seems to!
Read more >
Generic type arguments in JSX elements with withStyles-Reactjs
I'd make two modifications: (1) introduce an extra SFC to avoid a cast, and (2) grab the outer props type from the wrapped...
Read more >
Passing Generics to JSX Elements in TypeScript - Marius Schulz
TypeScript 2.9 added the ability to specify type arguments for generic JSX elements.
Read more >
TypeScript - Material-UI
The problem is that the type of the flexDirection property is inferred as string , which is too arbitrary. To fix this, you...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found