TypeScript: Can't pass props with excess properties

TypeScript Version: 2.3.1

Code

With the above mentioned version I can’t spread Props objects anymore to child React components, when the Props don’t match up 100%.


/* App.tsx */

interface Props {
    ...
    scopes: any;
}

interface State {}

export class App extends React.Component<Props, State> {
    render() {
         return <Content {...this.props} />; // error
    }
}

/* Content.tsx */

interface Props {
    ...
    // without scopes in Props
}

export default class Content extends React.Component<Props, {}>{
    ...
}

Expected behavior: No error and there wasn’t any on 2.2.1

Actual behavior:

error:

ERROR in ./spa/js/comps/App.tsx
(121,49): error TS2322: Type '{ ui?: UIState; session?: SessionState; scopes?: any; users?: any; roles?: any; plugins?: any; us...' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Content> & Props & { children?: ReactNode; }'.
  Property 'scopes' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<Content> & Props & { children?: ReactNode; }'.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 9
  • Comments: 56 (19 by maintainers)

Commits related to this issue

Most upvoted comments

Honestly, I just started using Typescript in a React project, and I’ve spent most of my time dealing with issues like this. It’s a shame.

A little bit different but still the same.

export default class Panel extends React.Component<void, void> {
...
}

now leads to this error

TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Panel> & Readonly<{ children?: ReactN...'. Type '{}' is not assignable to type 'void'.

Had to change void to any, null or undefined to make it work.

The same question. It’s hard to understand why the following code will not compile anymore in v2.3

import * as React from "react";

interface ComponentProps {
    property1: string;
    property2: number;
}

export default function Component(props: ComponentProps) {
    return (
        <AnotherComponent {...props} />
    );
}

interface AnotherComponentProps {
    property1: string;
}

function AnotherComponent({ property1 }: AnotherComponentProps) {
    return (
        <span>{property1}</span>
    );
}

due to the error

Type '{ property1: string; property2: number; }' is not assignable to type 'IntrinsicAttributes & AnotherComponentProps'.
  Property 'property2' does not exist on type 'IntrinsicAttributes & AnotherComponentProps'.

Btw, the same code without jsx will compile with no error

import * as React from "react";

interface ComponentProps {
    property1: string;
    property2: number;
}

export default function Component(props: ComponentProps) {
    return React.createElement<AnotherComponentProps>(AnotherComponent, props);
}

interface AnotherComponentProps {
    property1: string;
}

function AnotherComponent({ property1 }: AnotherComponentProps) {
    return React.createElement("span", null, property1);
}

I’m also having this exact same issue and I’ve spent the better part of the day scouring the internet trying to find what’s wrong since this issue doesn’t have an update. I’ve tried following different tutorials on different ways to connect redux with react.

For the ~16 hours I’ve spent today on this I’m coming to the conclusion that React + Redux + TS doesn’t work.

Typescript: 2.7.2

Sample:

(10,16): Property 'createBook' does not exist on type 'Readonly<{ children?: ReactNode; }> & Readonly<{}>'.

With this simple:

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

class Book extends React.Component {
  constructor(props: any){
    super(props);
  }
  
  submitBook(input: any){
    this.props.createBook(input);
  }
  
  render(){
    let titleInput;
    return(
      <div>
        <h3>Books</h3>
        <ul>
          {this.props.books.map((b, i) => <li key={i}>{b.title}</li> )}
        </ul>
        <div>
          <h3>Books Form</h3>
          <form onSubmit={e => {
            e.preventDefault();
            var input = {title: titleInput.value};
            this.submitBook(input);
            e.target.reset();
          }}>
            <input type="text" name="title" ref={node => titleInput = node}/>
            <input type="submit" />
          </form>
        </div>
      </div>
    )     
  }     
}     
    
// Maps state from store to props
const mapStateToProps = (state, ownProps) => {
  return {
    // You can now say this.props.books
    books: state.books
  }
};  
    
// Maps actions to props
const mapDispatchToProps = (dispatch) => {
  return {
  // You can now say this.props.createBook
    createBook: book => dispatch({
      type: 'ADD_BOOK',
      id: book.id
    })
  }
};

// Use connect to put them together
export default connect(mapStateToProps, mapDispatchToProps)(Book);

⬆️ One of a bunch of different ways I’ve tried.

I have the same problem as @levsthings, @mhegazy - the connect() method return type now seems to now require the props are set in the TSX when consuming the redux-connected component, whereas they are actually provided by react-redux.

UPDATE: This appears to be more of a problem with the latest react + react-redux typings, rather than typescript 2.3.4, since I get similar errors with TS 2.2.2

If anyone is able to decipher the issue with the typings please let me know! Heres a link… https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-redux/index.d.ts

For now I’m just throwing ‘as any’ after connect(…) 😄

Having a similar issue @ Typescript 2.3.4 Type '{}' is not assignable to type 'Readonly<ISpinnerProps>'. Property 'showGlobalSpinner' is missing in type '{}

Component 1:

interface ISpinnerProps{
    showGlobalSpinner: boolean
}
class SpinnerClass extends React.Component<ISpinnerProps, undefined> {
    render() {
         return (
         // Some markup
         )
    }
}

export const Spinner = connect((state: State) => {
  return {
    showGlobalSpinner: selectors.showGlobalSpinner(state),
  }
})(SpinnerClass)

Component 2:

interface IProps {
   someOtherProp: string
}
class AppClass extends React.Component<IProps, undefined> {
    render() {
         return (
         // Some markup
         <Spinner/> // Error is thrown here.
         )
    }
}
export const App = connect(mapStateToProps, actionCreators)(AppClass)

This pattern to used to compile fine earlier.

Update: Please note that the example props in both of these components are passed via Redux.

@dupski @giladaya @levsthings Can you comment on the PR? I think that’s a better place for it than this issue since it’s a different cause.

This seems to be the breaking commit: https://github.com/DefinitelyTyped/DefinitelyTyped/commit/2bc7feeb48e5c79fac1d4a09b36b5ce8c80896af Works in types/react-redux@4.4.40 and not in types/react-redux@4.4.41

I’m also having issues with this

interface RoleProps {
  radius?: number;
  size?: "small" | "medium" | "big";
  look?: "primary" | "secondary" | "dark" | "light";
}

export class Button extends React.PureComponent<RoleProps> {
  render() {
    return (
      <InternalButton  {...this.props} > {this.props.children} </InternalButton>
    )
  }
}

// InternalButton has P = <RoleProps & Themer>
 error TS2322: Type '{ children: ReactNode[]; radius?: number | undefined; size?: "big" | "small" | "medium" | undefin...' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<Pick<RoleProps & Themer, "radius" | "siz...'.
  Type '{ children: ReactNode[]; radius?: number | undefined; size?: "big" | "small" | "medium" | undefin...' is not assignable to type 'Readonly<Pick<RoleProps & Themer, "radius" | "size" | "look"> & HTMLProps<HTMLButtonElement> & Ex...'.
    Types of property 'size' are incompatible.
      Type '"big" | "small" | "medium" | undefined' is not assignable to type '(undefined & number) | ("big" & undefined) | ("big" & number) | ("small" & undefined) | ("small" ...'.
        Type '"big"' is not assignable to type '(undefined & number) | ("big" & undefined) | ("big" & number) | ("small" & undefined) | ("small" ...'.

That should technically work as the common props should spread.

it should be in TS 2.3.3 later this week.

@luca-moser @luggage66 @etecture-plaube @pomadin The reason for change in behavior is that in 2.3, JSXAttributes are type-check similar to object literal expression with freshness flag (this means that excess properties are not allowed) After discussing with @mhegazy, it is too restrictive and cases in examples here illustrate that. So we will make modification to the rule as follow:

  1. If there exist JSX attributes that are specified explicitly such as <div classNames="hi" /> or <div {…this.props} classNames=“hi” />, attributes objects will get freshness flag and undergo excess properties hceck

  2. If there are only spread in JSX attributes such as <div {...this.props} /> excess properties will be allowed

I will get the PR up today and the fix should be in for tomorrow nightly if you would like to give that a try!

This issue is the reason I’m choosing Flow for my next project.

Started using v 2.6.1 today and can still see a lot of errors similar to ones discussed here, i.e.

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, ...props } = this.props;
    return (
      <TouchableOpacity onPress={onPress} {...props}>
        <Icon source={src} width={width} height={height} /> /* errors */
      </TouchableOpacity>
    );
  }
}

export default TouchableIcon;

/* errors /* -> i not assignable to type IntrinsincAttributes …

this happens for all: src width an height

@levsthings this is also an issue when using mobx inject. Anyone have any insight on how to properly do this elegantly?

@giladaya You’re correct, I’ve also confirmed that the entire problem stems from types/react-redux@4.4.41.

@tsteuwer you should let Book to know that would be it’s props. that connected components will be exported, but not-connected-raw-Book component don’t know it’s type of Props

so if you do this

interface IBook {}
interface Props {
  books: IBook[],
  createBook: (book: IBook) => void
}
class Book extends React.Component<Props> {
  ...
}

then it will be ok

@mechanic22 It’s not perfect, but I just did return React.createElement(MyList); to solve this.

@ILL35T NavScroller extends React.Component<SportProps> so when rendering it you need to pass SportProps as its props, but you are not passing it any props and that is the reason for the error. If the props are optional then they need to be marked as such with ? or if you want to make them all optional then maybe extend React.Component<Partial<SportProps>>

@nileshgulia1 Property 'icon' is missing in type '{ label: "Force Update"; onClick: () => void; }' you need to either pass Button an ‘icon’ prop or mark it as optional on the ButtonProps interface.

@yuit I have compiled my code via build 2.4.0-dev.20170509, the error is gone. So I am waiting for v2.3.3 to move on.

@luca-moser @luggage66 @pomadin @etecture-plaube @IIIristraM We have fix for original in the master. It should be out for nightly tonight. Give that a try and let us know. We will have this fix for 2.3.3 as well.

@IIIristraM Note: the fix doesn’t address " Spread types may only be created from object types" I will get that in another PR -> Update we have the fix into master nightly. Give it a try and let us know

I agreed with @luca-moser, it’s common case to extend the props or to override some ones.

@yuit, the final solution looks acceptable as the 1st case (only explicitly attrs…) fits no jsx case.

interface Props {
    p1: string;
}

function fn(props: Props) {
}

fn({ p1: "", p2: 1 }); // ERROR: Object literal may only specify known properties...

You can do like below, but may get a warning about scopes being unused.

export class App extends React.Component<Props, State> {
    render() {
         const { scopes, ...otherProps } = this.props;
         return <Content {...otherProps} />;
    }
}