admin管理员组

文章数量:1401624

I'm trying to create a Form ponent in react, kind of like Formik, but way simpler.

The way I'm thinking involves adding a onChange handler to all the children. I am doing this with children.map(). It works, however I get a key warning

Warning: Each child in a list should have a unique "key" prop.

I know there's no way to suppress this, so maybe there's a better approach to create this Form ponent? Also, how should I approach the case when the <input> is not a direct child?

Edit: I know how to avoid the problem, I mainly want the best way to approach this, including cases of nested inputs.

Here is how I want to use it:

<Form>
  <label htmlFor="owner">Owner</label>
  <input
    type="text"
    name="owner"
    id="owner"
  />
  <label htmlFor="description">Description</label>
  <input
    type="text"
    name="description"
    id="description"
  />
  <input
    type="submit"
    value="Submit"
  />
</Form>

and here is my code:

import React from 'react';
    
class Form extends React.Component {
  constructor(props) {
    super(props);
    this.state = {}
    this.handleInputChange = this.handleInputChange.bind(this);
  }
    
  handleInputChange(event) {
    const target = event.target;
    const value =
      target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;
    console.log(`${name} : ${value}`)
    this.setState({
      [name]: value
    });
  }
    
  render() {
    return (
      <form>
        {this.props.children.map((child) => {
          if (child.type === "input") {
            return (
              <input
                onChange={this.handleInputChange}
                {...child.props}
              />
            )
          }
        })}
      </form>
    )
  }
}
    
export default Form;

I'm trying to create a Form ponent in react, kind of like Formik, but way simpler.

The way I'm thinking involves adding a onChange handler to all the children. I am doing this with children.map(). It works, however I get a key warning

Warning: Each child in a list should have a unique "key" prop.

I know there's no way to suppress this, so maybe there's a better approach to create this Form ponent? Also, how should I approach the case when the <input> is not a direct child?

Edit: I know how to avoid the problem, I mainly want the best way to approach this, including cases of nested inputs.

Here is how I want to use it:

<Form>
  <label htmlFor="owner">Owner</label>
  <input
    type="text"
    name="owner"
    id="owner"
  />
  <label htmlFor="description">Description</label>
  <input
    type="text"
    name="description"
    id="description"
  />
  <input
    type="submit"
    value="Submit"
  />
</Form>

and here is my code:

import React from 'react';
    
class Form extends React.Component {
  constructor(props) {
    super(props);
    this.state = {}
    this.handleInputChange = this.handleInputChange.bind(this);
  }
    
  handleInputChange(event) {
    const target = event.target;
    const value =
      target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;
    console.log(`${name} : ${value}`)
    this.setState({
      [name]: value
    });
  }
    
  render() {
    return (
      <form>
        {this.props.children.map((child) => {
          if (child.type === "input") {
            return (
              <input
                onChange={this.handleInputChange}
                {...child.props}
              />
            )
          }
        })}
      </form>
    )
  }
}
    
export default Form;
Share Improve this question edited Feb 5, 2022 at 17:31 Ledorub 4665 silver badges10 bronze badges asked Jun 15, 2019 at 13:56 LorenzoLorenzo 2,21013 silver badges29 bronze badges 3
  • You might want to consider using context instead, if you are worried about inputs not being direct children. You cannot reliably traverse further down than children from a ponent, but you can inject context at any level. – Wolfie Commented Jun 15, 2019 at 14:21
  • Possible duplicate of Understanding unique keys for array children in React.js – Anurag Srivastava Commented Jun 15, 2019 at 14:22
  • @AnuragSrivastava I do indeed know how to avoid the warning, I'm asking for a better way to actually eliminate the problem, plus also dealing with nested inputs – Lorenzo Commented Jun 15, 2019 at 18:23
Add a ment  | 

2 Answers 2

Reset to default 4

If you use a render prop, you won't run into the unique "key" prop issue at all (This is also how Formik is implemented).

Your ponent would be easy to set up to pass handleChange to its children as a render prop, and this would also not require you to have input as a direct child.

class Form extends Component {
  ...
  handleInputChange() {...}

  render() {
    // Note that we call children as a function,
    // passing `handleChangeInput` as the argument.
    // If you want to pass other other things to the
    // children (handleSubmit, values from state), just
    // add them to the argument you're passing in.
    this.props.children({this.handleInputChange});
  }
}

Here's how you use it:

<Form>
  // Notice that <Form> needs its immediate child to be
  // a function, which has your handler as the argument:
  {({handeInputChange}) => {
    return (
      <form>
        <input type="text" name="owner" onChange={handleInputChange} />
        <input type="checkbox" name="toggle" onChange={handleInputChange} />
        <div>
          // inputs can be nested in other elements
          <input name=“inner” onChange={handleInputChange} />
        <div>
      <form>
    )  
  }}
</Form>

EDIT: You mentioned in a ment that you didn't want to explicitly pass the handler to each of your inputs. Another way to achieve this is with React Context, with a Provider in your Form, and each input wrapped in a consumer:

const FormContext = React.createContext();

const FormInput = (props) => {
  const {handleInputChange} = useContext(FormContext);
  return <input handleInputChange={handleInputChange} {...props} />
}

class Form extends Component {
  ...
  handleInputChange() {...}

  render() {
    // Pass anything you want into `value` (state, other handlers),
    // it will be accessible in the consumer 
    <Provider value={{ handleInputChange: this.handleInputChange }}>
      <form>
        {this.props.children}
      </form>
    </Provider>
  }
}

// Usage:
<Form>
  <FormInput type="text" name="owner" />
  <FormInput type="submit" name="submit" />
  <div>
      <FormInput type="checkbox" name="toggle" />
  </div>
</Form>

In fact, Formik has this option as well, with either the Field ponent, or the connect function.

i think this is what you need, already you could add child index as key since there order won't change, and reduce here is not returning null in the array in case the type of the child is not input, map+filter could resolve it also:

class Form extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
    this.handleInputChange = this.handleInputChange.bind(this);
    // this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === "checkbox" ? target.checked : target.value;
    const name = target.name;
    console.log(`${name} : ${value}`);
    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        {this.props.children.reduce((childrenAcc, child, index) => {
          if (child.type === "input") {
            return [
              ...childrenAcc,
              <input
                key={index}
                onChange={this.handleInputChange}
                {...child.props}
              />
            ];
          }
          return childrenAcc;
        }, [])}
      </form>
    );
  }
}

function App() {
  return (
    <Form>
      <label htmlFor="owner">Owner</label>
      <input type="text" name="owner" />
      <label htmlFor="description">Description</label>
      <input type="text" name="description" />
      <input type="submit" value="Submit" />
    </Form>
  );
}

check this sandbox .

本文标签: javascriptREACTAttach event handler to childrenStack Overflow