Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass rest of props to react component while also having required props defined in an interface

So is this the same issue, or am I just missing something?

import * as React from 'react';

interface Props {
    value: string;
}

const MyComponent = (props: Props) => {
    const { value, ...rest } = props;

    return (
        <input {...rest} type="text" value={value} />
    );
}

interface ParentState {
    searchText: string;
}

class ParentComponent extends React.Component<{}, ParentState> {
    state: ParentState = {
        searchText: ''
    };

    onSearchTextChanged = (e: React.FormEvent<HTMLInputElement>) => {
        this.setState({
            searchText: e.currentTarget.value
        });
    }

    render() {
        const { searchText } = this.state;

        return (
            <div>
                <h2>Some Text</h2>
                <MyComponent value={searchText} onChange={this.onSearchTextChanged} className="search-input" placeholder="Enter text"/> 
// Errors here
            </div>
        )
    }
}

export default ParentComponent

When I have my props for MyComponent defined in an interface, I get the following error:

error TS2339: Property 'onChange' does not exist on type 'IntrinsicAttributes & Props'.

However if I change the props type to any, it compiles just fine.

const MyComponent = (props: any) => {

Is it possible to define the props in an interface so that there are specific props that are required, but also allow additional props to be passed in so I don't have to explicitly add them into the interface?

I'm using TypeScript 2.3.4 and React 15.5.4.

like image 762
Rabbit G Avatar asked Dec 08 '22 18:12

Rabbit G


2 Answers

You can avoid excess property/attribute checks by adding a string index signature to your interface:

interface Props {
    value: string;

    // This is a string index signature.
    // This means that all properties in 'Props' are assignable to
    // the empty object type ({})
    [propName: string]: {};
}

You could also write [propName: string]: any but that generally makes it less safe to use in MyComponent itself.

like image 158
Daniel Rosenwasser Avatar answered Dec 11 '22 07:12

Daniel Rosenwasser


If you want to forward all properties of the input element inside MyComponent to the props of MyComponent you can lookup what props input (in VSCode for example, use go to definition while on the input tag).

interface IntrinsicElements {
   ....
   input: React.DetailedHTMLProps<React.InputHTMLAttributes<>, HTMLInputElement>;
   ....
}

We could use React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> as the base type for our props and we will get all properties of input. Since DetailedHTMLProps just adds ref to React.InputHTMLAttributes<HTMLInputElement> we can use just React.InputHTMLAttributes<HTMLInputElement> as the base interface to get all input properties except the ref:

import * as React from 'react'

interface Props extends React.InputHTMLAttributes<HTMLInputElement> {
    value: string;
}

const MyComponent = (props: Props) => {
    const { value, ...rest } = props;

    return (
        <input {...rest} type="text" value={value} />
    );
}

// Usage
interface ParentState  { searchText :string }
class ParentComponent extends React.Component<{}, ParentState> {
  state: ParentState = {
      searchText: ''
  };

  onSearchTextChanged = (e: React.FormEvent<HTMLInputElement>) => {
      this.setState({
          searchText: e.currentTarget.value
      });
  }

  render() {
      const { searchText } = this.state;

      return (
          <div>
              <h2>Some Text</h2>
              <MyComponent value={searchText} onChange={this.onSearchTextChanged} className="search-input" placeholder="Enter text"/> 
          </div>
      )
  }
}

export default ParentComponent
like image 41
Titian Cernicova-Dragomir Avatar answered Dec 11 '22 08:12

Titian Cernicova-Dragomir