1

I often find myself in the situation of needing to make a component accept any valid HTML property to make the underlying HTML element use them.

class Input extends React.Component {
  // Here I use one of the `props` to add logic to
  // my component
  onChange = () => alert(this.props.alert);

  render() {
    // Here I want to pass all the properties
    // down to the HTML input element
    return <input onChange={this.onChange} {...this.props} />
  }
}

My above example will throw a warning because React complains about the alert property not being a valid one for input.

I usually workaround the problem doing as follows:

class Input extends React.Component {
  // Here I use one of the `props` to add logic to
  // my component
  onChange = () => alert(this.props.alert);

  render() {
    const {
      alert, // eslint-disable-line no-unused-vars
      ...props
    } = this.props;

    // Here I want to pass all the properties
    // down to the HTML input element
    return <input onChange={this.onChange} {...props} />
  }
}

Now the component works properly but I don't feel comfortable with the end result.

I understand why React doesn't allow to pass unknown properties to its primitives, since they are not going to use them and it's not clean to provide useless properties that in the future may become valid HTML properties or lead to misbehaviors or side effects.
But I don't understand how React suggests to handle this (pretty common) use case.

Is there any suggested way to handle this case in a clean manner?

Fez Vrasta
  • 11,462
  • 19
  • 73
  • 135
  • I see that libraries like react-css-modules define their own props as not enumerable to workaround this problem, but I'm not aware of any method to declare props as not enumerable using Rect. – Fez Vrasta Apr 23 '17 at 12:40

2 Answers2

0

Other than your approach, which is mostly what I've seen done (but I've only seen a few shops' styles), the two other approaches that come to mind are:

  1. Have the general properties supplied separately:

    <Input alert={...} general={{className: "foo"}} />
    

    then

    return <input onChange={this.onChange} {...this.props.general} />;
    

    To which I say: Blech. Burdensome where you use Input, doesn't compose well...

  2. Give yourself a utility function you can use to copy an object leaving out certain properties, rather like Underscore/Lodash's _.omit:

    const objectExcept(obj, ...except) {
        const result = {};
        Object.keys(obj).forEach(key => {
            if (!except.includes(key)) {
                result[key] = obj[key];
            }
        });
        return result;
    };
    

    then

    return <input onChange={this.onChange} {...objectExcept(this.props, "alert")} />;
    

    That version of objectExcept takes discrete arguments, but you could have it accept an array, a delimited string, whatever works for you...

T.J. Crowder
  • 879,024
  • 165
  • 1,615
  • 1,639
  • The `objectExcept` is a solution. Maybe something like this is less problematic tho? https://codepen.io/FezVrasta/pen/dWXwBB The problem is that both are heavier and add logic that can be avoided with the ugly-but-fast workaround I provided – Fez Vrasta Apr 23 '17 at 13:39
  • 1
    @FezVrasta: That works too. (You don't need the `delete`.) But to me that's more work than we need. Re "heavier," I don't think it is, actually, *unless* you're using `alert` for something in `render`. If you were, you wouldn't have the ESLint thing to worry about and destructuring is the right choice. But for this situation where you aren't using it, the simple "give me this object without these properties" of `objectExcept` above is less typing and only the same amount of work (roughly) at runtime. – T.J. Crowder Apr 23 '17 at 13:45
-1

Perhaps I've misunderstood, but in order to exclude certain props, can't you just use destructuring?

  render() {
    const {foo, bar} = this.props;
    const inputProps = {foo, bar}

    return <input onChange={this.onChange} {...inputProps} />
  }

Then you can use this.props.alert elsewhere in your Input class.

Or even, if you use spreads with babel:

const {alert, ...inputProps} = this.props

This will omit alert from a new inputProps object. More info here: clone a js object except for one key

Community
  • 1
  • 1