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

Can't pass props with excess properties #15463

Closed
luca-moser opened this issue Apr 29, 2017 · 56 comments
Closed

Can't pass props with excess properties #15463

luca-moser opened this issue Apr 29, 2017 · 56 comments
Labels
Bug A bug in TypeScript Domain: JSX/TSX Relates to the JSX parser and emitter Fixed A PR has been merged for this issue

Comments

@luca-moser
Copy link

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; }'.
@luggage66
Copy link

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} />;
    }
}

@mhegazy mhegazy added the Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature label May 1, 2017
@pomadin
Copy link

pomadin commented May 4, 2017

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

@etecture-plaube
Copy link

etecture-plaube commented May 4, 2017

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.

@yuit yuit self-assigned this May 4, 2017
@yuit yuit added this to the TypeScript 2.3.3 milestone May 4, 2017
@yuit
Copy link
Contributor

yuit commented May 4, 2017

@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!

@luca-moser
Copy link
Author

luca-moser commented May 4, 2017

@yuit If you only allow the second case in your list, it will still break a lot of components. Parent components often spread their Props object further and compute some additional things which are passed explicitly.

Here's an example from my actual codebase:

this.props.users.sort((a, b) => a.name > b.name ? 1 : -1).forEach((user: User) => {
    if (user._filtered) return;
    users.push(<UserCard {...this.props} key={user.id} user={user}/>); // this would error out
});

@yuit
Copy link
Contributor

yuit commented May 4, 2017

@luca-moser Thanks for feed back! Here is the second thought :

  1. Only contain explicitly attributes -> no excess properties allowed
  2. Contain spread attributes (even with explicit attributes) -> 1. check type assignability (allow access properties) 2. for each explicit attributes check if the attribute name match the target name.

@yuit yuit added Bug A bug in TypeScript Domain: JSX/TSX Relates to the JSX parser and emitter and removed Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature labels May 4, 2017
@yuit yuit mentioned this issue May 5, 2017
1 task
@pomadin
Copy link

pomadin commented May 5, 2017

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...

@IIIristraM
Copy link

IIIristraM commented May 5, 2017

I also have a lot of Spread types may only be created from object types. for { ...this.props } in cases that worked well in 2.2.2,

For case <Component someProperty={this.privateMethod} /> I get 'privateMethod' is declared but never used.. If I pass the property like <Component { ...{someProperty: this.privateMethod}} /> or make method public, the error is gone

@yuit
Copy link
Contributor

yuit commented May 5, 2017

@IIIristraM could you share more repo for both cases. the Second may get fixed in the nightly. IF you could share a full repo, I can take a look

@IIIristraM
Copy link

IIIristraM commented May 5, 2017

import { inject, observer } from 'mobx-react';
import * as React from 'react';
import { ProviderContext } from 'types';

interface IMapFunction<TInnerProps, TOuterProps> {
  (context: ProviderContext, nextProps?: TOuterProps): TInnerProps;
}

export default <TInnerProps, TOuterProps>(mapStore: IMapFunction<TInnerProps, TOuterProps>) => (
  (Component: React.ComponentClass<TInnerProps>) => {
    const Observer = observer(Component);

    @inject(mapStore)
    class Connector extends React.Component<TInnerProps & TOuterProps, {}> {
      public node: React.Component<TInnerProps, any>;

      private setNode = (n: React.Component<TInnerProps, any>) => {
        this.node = n;
      }

      public render() {
        return <Observer { ...this.props } ref={this.setNode} />;
      }
    }

    return Connector as React.ComponentClass<TOuterProps>;
  }
);

both cases take place in this example

@yuit
Copy link
Contributor

yuit commented May 5, 2017

Where an error occur? Is it at here return <Observer { ...this.props } ref={this.setNode} />; ? can you share that class declaration if that is the case.

@IIIristraM
Copy link

IIIristraM commented May 5, 2017

Yes, this is right code line.
Component can be an any React component, observer(Component) returns typeof Component

Definitions for React.Component andobserver are taken from definitely-typed repo.

@yuit
Copy link
Contributor

yuit commented May 5, 2017

@IIIristraM Thanks! I am able to see this error Spread types may only be created from object types...this is consistent with how the spread type work in our system at the moment. Though I am talking with @sandersn @mhegazy later to see what we can do about that (we have this PR which we will discuss about ) we have others running into these (#14112 (comment))

@yuit
Copy link
Contributor

yuit commented May 8, 2017

@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

@yuit yuit added the Fixed A PR has been merged for this issue label May 8, 2017
@pomadin
Copy link

pomadin commented May 10, 2017

@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.

@yuit
Copy link
Contributor

yuit commented May 10, 2017

@pomadin thanks for the feedbacks. We will have this fix in for 2.3.3

@timocov
Copy link
Contributor

timocov commented May 15, 2017

export function makeP<P>(Ctor: React.ComponentClass<P>): React.ComponentClass<P> {
    return class extends React.PureComponent<P, void> {
        public render(): JSX.Element {
            return (
                <Ctor {...this.props }/>
            );
        }
    };
}

@yuit for 2.3.3-insiders.20170512 it still does not work (and for 2.4.0-dev.20170515 too).

@kidqn
Copy link

kidqn commented Jul 26, 2017

Hi @pomadin , I met the same problem and then found the reason that is
you SHOULD pass value for those parameter in use.
For example:
<Component property1={'parents'} property2={'kids'} />
If you delete "property1={'parents'} property2={'kids'}" the TS will notify error

@nuviosoftware
Copy link

nuviosoftware commented Oct 15, 2017

I had the same issue which I resolved by extending JSX.IntrinsicAttributes adding a data field to it:

interface JSXData<T> extends JSX.IntrinsicAttributes {
  data : T;
}


const Row = (props: JSXData<BalanceEntry>) : JSX.Element => {
  return (
    <tr>
      <td>
        {props.data.name}
      </td>
      <td>
        {props.data.nett}
      </td>
      <td>
        {props.data.tax}
      </td>
    </tr>
  );
}


class BalanceEntry {
  name: string;
...
  nett: number;
  tax: number;
}

so I could render the table as follows:


 render() {
    let rows = this.data.map(entry => {
      return <Row
          key={entry.id}
          data={entry} 
        />
    });

    return (
    <table className="table table-striped">
      <thead>
        <tr>
          <th>Entry</th>
          <th>Nett</th>
          <th>Tax</th>
        </tr>
      </thead>
      <tbody>{rows}</tbody>
    </table>
    );
  }

@xzilja
Copy link

xzilja commented Nov 2, 2017

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

@mhegazy
Copy link
Contributor

mhegazy commented Nov 2, 2017

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

I am not familiar with styled-components, but seems like you have Props requiring src and onPress. and that is the err you are getting.. the error is not about src, width and height, it is about missing properties.

@quantuminformation
Copy link

I just added react-router-dom to a https://github.com/wmonk/create-react-app-typescript app and added the following code and got a similar error:

image

any ideas?

@iKBAHT
Copy link

iKBAHT commented Nov 10, 2017

@quantuminformation use RouteComponentProps from 'react-router' in your Home component

export class Home extends React.Component<RouteComponentProps<void>, any>

@quantuminformation
Copy link

I'm using ConnectedRouter now and it just works, even with just:

const Home = () => (
  <div>
    <h2>Home</h2>
  </div>
)

@seivan
Copy link

seivan commented Dec 21, 2017

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.

@kevinSuttle
Copy link

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.

@sandersn
Copy link
Member

@kevinSuttle can you file a new issue? This is labelled fixed. In case it's not fixed, can you explain how the error reproduces in your code?

@seivan
Copy link

seivan commented Jan 23, 2018

@sandersn Is this not sufficient enough for you #15463 (comment) ?

@sandersn
Copy link
Member

@seivan What is the type of Themer? The intersection of RoleProps & Themer is too big to print in an error message, it looks like.

@ILL35T
Copy link

ILL35T commented Jan 23, 2018

@levsthings @mhegazy @sandersn Hey guys I am also stuck on this, been bashing my head all week.
I am new to react, redux and typescript and I have been following the SPA template for React and Redux in VS2017.

I keep getting this error:

ERROR in [at-loader] ./ClientApp/components/Layout.tsx:9:15
TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes & Readonly<{ children?: ReactNode; }>...'.
Type '{}' is not assignable to type 'Readonly'.
Property 'isLoading' is missing in type '{}'.

import * as React from 'react';
import { Link } from 'react-router-dom';
import { LinkContainer } from 'react-router-bootstrap';
import { Nav } from 'react-bootstrap';
import * as SportsState from '../store/Sport';
import { RouteComponentProps } from 'react-router';
import { ApplicationState } from '../store';
import { connect } from 'react-redux';

// At runtime, Redux will merge together...
type SportProps =
    SportsState.SportsState        // ... state we've requested from the Redux store
    & typeof SportsState.actionCreators      // ... plus action creators we've requested
    & RouteComponentProps<{}>;


export class NavScroller extends React.Component<SportProps> {
    componentWillMount() {
        // This method runs when the component is first added to the page
        //this.props.requestSports();
    }

    public render() {
        return <div className="nav-scroller bg-white box-shadow mb-4">
            <Nav className="nav nav-underline" >
                {this.props.sports && this.props.sports.map(sport =>
                    <Link className='nav-link' to={`/Sport/${sport.id}`}>
                        <img src={`../images/icons/${sport.name}.svg`}/>{sport.name}
                    </Link>
                )} 
            </Nav>
        </div>
    }
}
export default connect(
    (state: ApplicationState) => state.sports, // Selects which state properties are merged into the component's props
    SportsState.actionCreators                 // Selects which action creators are merged into the component's props
)(NavScroller) as any;
import * as React from 'react';
import { NavMenu } from './NavMenu';
import { NavScroller } from './NavScroller';

export default class Layout extends React.Component<{}> {
    public render() {
        return <div>
            <NavMenu />
            <NavScroller/> <-----ERROR IS HERE
            {this.props.children}
        </div>;
    };
}

@braineo
Copy link

braineo commented Jan 26, 2018

Same here using React + Redux + Material-UI-Next. Seems those props are not injected properly.

In my case, withStyles should have injected classes but I got Property 'classes' is missing in type '{ sample: SampleState; }'

import withStyles, { WithStyles } from 'material-ui/styles/withStyles';
type PropsWithStyle = Props & WithStyles<'root'>
// WithStyle introduces classes property
class Controller extends React.Component<PropsWithStyle , State> {}
export default withWidth()(withStyles(styles)(connect(mapStateToProp)(SampleComponent)));

@nileshgulia1
Copy link

Hey guys I got the same problem too,please help me

<Button label="Force Update" onClick={() => {
          // this.setState({splits});
          setInterval(() => {
            let width = this.state.width - 10;
            this.setState({width});
          }, 100);
          
        }}></Button>
 Type '{ label: "Force Update"; onClick: () => void; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Button> & Readonly<{children?: ReactNode; }> & Re...'.
  Type '{ label: "Force Update"; onClick: () => void; }' is not assignable to type 'Readonly<{ icon: Element; label?: string; title?: string; isDisabled?: boolean; onClick?: Functio...'.
    Property 'icon' is missing in type '{ label: "Force Update"; onClick: () => void; }'.

ERROR in [at-loader] ./src/util.ts:81:19

@frankwallis
Copy link
Contributor

@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.

@seivan
Copy link

seivan commented Feb 3, 2018

@sandersn Sorry to get back to you so late.

I made it simple for testing, it's just

export interface Themer {
    theme: { color: string }
}

What I am trying to figure out that is ambiguous and unclear (a failure I attribute to TypeScript or at least its documentation) is out the spread operator works overall.

Perhaps you could help clear that up here, and we could take a look at the documentation to make it less ambiguous.

#15463 (comment)

This seems like a bug (but obviously I could be wrong)

Pick<RoleProps & Themer, "radius" | "size" | "look">

If we're picking properties then Themer should be irrelevant.

@activebiz
Copy link

activebiz commented Feb 6, 2018

I am having same issue but only when I saperate out component and containers!

Really fustrating :|

@mechanic22
Copy link

mechanic22 commented Feb 13, 2018

Yes, I too am having the issue. If you pull in a container that gets its properties from a redux store, you cannot compile without specifying the properties. An example is it seems to occur with typescript and when you cast the connect back to the base type

connect((state: ApplicationState) => state.myState, actionCreators)(MyList) as typeof MyList;

If I do this to create a container and then use it in a page like this


import * as React from 'react';
import MyList from './MyList'

class MyPage extends React.Component {
    render() {
      return <MyList />;  // <--- I will get the error here
    }
}
export default MyPage ;

If I leave off the 'as typeof MyList' in the connect of the container then the error goes away. However I lose being able to have type on the container

@tsteuwer
Copy link

tsteuwer commented Mar 4, 2018

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.

@PaitoAnderson
Copy link

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

@FourwingsY
Copy link

FourwingsY commented Mar 12, 2018

@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

@sakalys
Copy link

sakalys commented May 24, 2018

@levsthings I got the same issue, but I don't see any solution apart from what @PaitoAnderson suggested (return React.createElement(MyList); -- that worked). So what about a proper way?

@Peterabsolon
Copy link

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

@seivan
Copy link

seivan commented May 28, 2018

@Peterabsolon Flow will have its own set of problems and not give the same runtime guarantees as TS. But yeah, the compiler errors could use some more work :)

@sandersn sandersn changed the title is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes Can't pass props with excess properties May 29, 2018
@sandersn
Copy link
Member

@seivan I looked at your example again and I noticed that InternalButton wasn't defined, so I added an empty definition:

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

The error I get is then "Property 'theme' is missing in '{ children: ReactNode[], radius?: number, size?: "small" ... }'." I can fix it by passing a theme attribute to InternalButton, which makes sense because `InternalButton requires all the fields from both RoleProps and Themer:

    return <InternalButton {...this.props} theme={{ color: 'brown' }}>{this.props.children}</InternalButton>

Note that I don't know React that well -- I'm just following the types, so I don't know if theme is actually required at runtime. As far as I can tell, the above is equivalent to the typescript function calls:

function InternalButton(props: RoleProps & Themer) { }
function Button(props: RoleProps) {
  return InternalButton({ ...props, theme: { color: 'brown' } })
}

And for those definitions, it makes sense to me that theme is required.

@microsoft microsoft locked and limited conversation to collaborators Jul 31, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bug A bug in TypeScript Domain: JSX/TSX Relates to the JSX parser and emitter Fixed A PR has been merged for this issue
Projects
None yet
Development

No branches or pull requests