dpaulus
dpaulus

Reputation: 422

Joining React props with custom props

When defining props in React, using Typescript, it seems that the default React props get overwritten by whatever the interface is. Is there a clean way to merge the two without having to specify every prop React already knows about?

Example:

interface IProps { // extends React.???
   title: string;
   // Generally seen adding children?: any
   // But this can get out of hand with onClick, onChange, etc
}

function MyComponent(props: IProps) {
   return props.children; // Typescript error: children doesn't exist on props
}

Upvotes: 2

Views: 3317

Answers (3)

localghost
localghost

Reputation: 1

type TitleProps = { // your custom props
  level?: level;
  color?: string;
};

const Title = (props: TitleProps & React.Component['props']) => { // join default props and custom props
    const { level = 'h1', color, children } = props; // joined props containing default props
    return <Text style={[styles[level], color && { color }]}>{children}</Text>;
}

I was able to solve the problem by this way.

Upvotes: 0

Andy
Andy

Reputation: 8640

What you're referring to as "React default props" aka "every prop React already knows about" are more properly called "props accepted by any React DOM element wrapper component", i.e. onClick, className, etc.

Default props typically refers to the static defaultProps property on a React.Component, with which you provide default values for any props that were not passed to your component.

onClick, className, etc. are not reserved prop names and you can use them however you want in your own components, for instance you could have your component expect className to be a function (regardless of whether it's a good idea). The only reserved prop names that work on React elements of any kind (at the time of writing) are key and ref, and they're not really true props because they're not available to your component's render method.

Passing onClick to your own component does not automatically register a click handler. It will only do so if you pass the onClick you received to a <div>, <button>, or other React DOM Element wrapper that you render somewhere down the line. If you don't do anything with a prop you were passed, it has no effect (besides possibly causing a pure render component to update when it otherwise wouldn't).

For example, the following component will not respond to clicks:

const ClickFail = props => <button />
render(<ClickFail onClick={...} />, document.getElementById('root'))

But the following will:

const ClickSuccess = props => <button onClick={props.onClick} />
render(<ClickSuccess onClick={...} />, document.getElementById('root'))

And you could pass onClick to only one subelement if you really wanted:

const ClickButtonOnly = props => (
  <form>
    <input placeholder="ignores clicks" />
    <button onClick={props.onClick}>Handles Clicks</button>
  </form>
)

Or you could pass in multiple click handlers with different names:

const SimpleForm = props => (
  <form>
    <button onClick={props.onCancelClick}>Cancel</button>
    <button onClick={props.onOKClick}>OK</button>
  </form>
)

Also keep in mind that some DOM element wrappers accept props that others do not, for instance readOnly applies only to <input> and <textarea>.

You can even require children to be whatever type you want. For instance, you can pass a function as the children of a component and use it (again, not the best use of React, but just to illustrate what's possible):

type Props = {
  value: number,
  children: (value: number) => number,
}
const ApplyFunction = (props: Props) => (
  <div>{React.Children.only(props.children)(props.value)}</div>
)

render(
  <ApplyFunction value={3}>
    {value => value * value}
  </ApplyFunction>,
  document.getElementById('root')
)
// renders <div>9</div>

So you see, IProps does not necessarily have to extend anything.

However, it is common to pass along rest props to a React DOM Element wrapper (e.g. <div {...props}>...</div> and as you were asking, it would be nice to be able to check the type of all of those input properties to your component.

I think you could do the following with Flow to check the types correctly, but unfortunately I don't think there's any Typescript equivalent (someone correct me if I'm wrong):

type Props = React.ElementProps<typeof 'div'> & {
  title: string,
}
const MyComponent = (props: Props) => (
  <div {...props}>
    {props.title}
  </div>
)

Upvotes: 2

paibamboo
paibamboo

Reputation: 2904

You should define that your stateless functional component will return React.SFC<YourProps>.

Try this

import * as React from "react";
const MyComponent: React.SFC<IProps> = (props) => {
  return props.children;
}

If you want to use class-based component, you can extend your class with React.Component<YourProps(optional), YourState(optional)> instead

For example

import * as React from "react"
class MyComponent extends React.Component<IProps> {
  public render(): JSX.Element {
    return (
      <div>...</div>
    );
  }
}

Upvotes: 0

Related Questions