【问题标题】:Passing handleSubmit() to child component does not modify parent's state将 handleSubmit() 传递给子组件不会修改父组件的状态
【发布时间】:2019-11-21 17:56:01
【问题描述】:

我是 React 和 Javascript 的新手。

我正在尝试让用户填写一份描述“暴民”应该是什么样子的表格。当用户点击提交时,我希望handleSubmit()(通过父级传入)修改父级的状态,这是一个对象。但是,这种行为并没有发生。

这里是父组件,叫做 App。

class App extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            mob: new Mob("", "")
        };

        this.handleSubmit = this.handleSubmit.bind(this);
    }

    handleSubmit(event) {
        event.preventDefault();
        alert("A name was submitted: " + this.state.vnum + " event value: " + event.state.vnum);
        const newMob = new Mob(event.state.vnum, event.state.shortDesc);

        this.setState({
            mob: newMob
        });
    }

    render() {
        return (
            <div>
                <MobForm mob={this.state.mob} onSubmit={() => this.handleSubmit} />
                {console.log("parsed mob vnum: " + this.state.mob.vnum)}
            </div>
        );
    }
}

子组件,称为 MobForm

class MobForm extends React.Component {
    render() {
        return (
            <div>
                <form onSubmit={this.props.onSubmit}>
                    <CreateStringInputField
                        name="vnum"
                        label="vnum:"
                    />
                    <CreateStringInputField
                        name="shortDesc"
                        label="Short Desc:"
                    />
                    <input type="submit" value="Submit" />
                </form>
                {console.log(this.state)}
            </div>
        );
    }
}

谁在打电话CreateStringInputField()

function CreateStringInputField(props) {
    return (
        <div name="row">
            <label>
                <b>{props.label}</b>
                <br />
                <input
                    type="text"
                    name={props.name}
                    label={props.label}
                />
            </label>
        </div>
    );
}

而且,如果重要的话,这就是“暴民”的样子。

class Mob {
    constructor(vnum, shortDesc) {
        this.vnum = vnum;
        this.shortDesc = shortDesc;
    };
}

我希望看到{console.log("parsed mob vnum: " + this.state.mob.vnum)} 打印出用户输入的 vnum。相反,我什么也没看到。我怎样才能达到这个预期的输出?

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    我能够通过向 MobForm 传递一个更新 this.state.mob 的函数来获得我想要的行为。

    应用

    class App extends React.Component {
        state = {
            mob: new Mob("", "")
        };
    
        updateMob = newMob => {
            this.setState({
                mob: newMob
            });
        };
    
        render() {
            return (
                <div>
                    <MobForm mob={this.state.mob} onSubmit={this.updateMob} />
                </div>
            );
        }
    }
    

    然后我让 MobForm 维护 vnum, shortDesc 状态,我可以在我的 onChange() 中使用它

    MobForm

        state = { vnum: "", shortDesc: "" };
    
        handleSubmit = event => {
            event.preventDefault();
            const mob = new Mob(this.state.vnum, this.state.shortDesc);
            this.props.onSubmit(mob);
        };
    
            render() {
            return (
                <div>
                    <form onSubmit={this.handleSubmit}>
                        <CreateStringInputField
                            name="vnum"
                            value={this.state.vnum}
                            onChange={event => this.setState({ vnum: event.target.value })}
                        />
                        <CreateStringInputField
                            name="short desc"
                            value={this.state.shortDesc}
                            onChange={event => this.setState({ shortDesc: event.target.value })}
                        />
                        <input type="submit" value="Submit" />
                    </form>
                </div>
            );
        }
    }
    

    【讨论】:

    • 我不确定你为什么将你的状态存储到一个类中。对于当前的实现,我看不到任何好处。如果您希望将字段存储为对象,那么您可以简单地使用一个对象:codesandbox.io/s/simple-form-czfnr
    【解决方案2】:

    使用 React,您无需使用普通类。相反,extends 类是提供的 React 组件(ComponentPureComponent,如果您不需要 state,则将使用仅返回一些 JSX 的普通函数。

    工作示例https://codesandbox.io/s/simple-form-kdh3w


    index.js

    import React from "react";
    import { render } from "react-dom";
    import MobForm from "./components/MobForm";
    
    // simple function that returns "MobForm" and it gets rendered by ReactDOM
    function App() {
      return <MobForm />;
    }
    
    // applies "App" to a <div id="root"></div> in the public/index.html file
    render(<App />, document.getElementById("root"));
    

    components/MobForm/index.js(有状态的父组件)

    import React, { Component } from "react";
    import Form from "../Form";
    
    const initialState = {
      vnum: "",
      shortDesc: ""
    };
    
    // a stateful parent that manages child state
    class MobForm extends Component {
      constructor(props) {
        super(props);
        this.state = initialState;
    
        // since the class fields are normal functions, they'll lose context
        // of "this" when called as a callback. therefore, they'll need 
        // to be bound to "this" -- via bind, "this" is now referring to 
        // the Class, instead of the global window's "this")
        this.handleChange = this.handleChange.bind(this);
        this.handleReset = this.handleReset.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
      }
    
      // a reusable class field that stores an input's value via its "name"
      // for example: [vnum]: "12345", [shortDesc]: "A number"
      // using object destructuring for shorter syntax:
      // [event.target.name]: event.target.value
      handleChange({ target: { name, value } }) {
        this.setState({ [name]: value });
      }
    
      // a class field to reset state
      handleReset() {
        this.setState(initialState);
      }
    
      // a class field to "submit" the form and alert what's currently in state
      handleSubmit(event) {
        // preventDefault prevents page refreshes
        event.preventDefault(); 
    
        // JSON.stringify allows you to print the contents of an object
        // otherwise, you'll just see [object Object]
        alert(JSON.stringify(this.state, null, 4)); 
    
        // clears state after submitting form
        this.handleReset(); 
      }
    
      render() {
        return (
          // passing down state via the spread operator, shorthand for 
          // "vnum={this.state.vum}" and "shortDesc={this.state.shortDesc}",
          // as well as, passing down the class fields from above
          <Form
            {...this.state}
            handleChange={this.handleChange}
            handleReset={this.handleReset}
            handleSubmit={this.handleSubmit}
          />
        );
      }
    }
    
    export default MobForm;
    

    components/Form/index.js(返回某种形式 JSX 的子函数)

    import React from "react";
    import PropTypes from "prop-types";
    import Input from "../Input";
    
    // using object destructuring to pull out the MobForm's passed down
    // state and fields. shorthand for using one parameter named "props" 
    // and using dot notation: "props.handleChange", "props.handleReset", etc
    function Form({ handleChange, handleReset, handleSubmit, shortDesc, vnum }) {
      return (
        <form style={{ width: 200, margin: "0 auto" }} onSubmit={handleSubmit}>
          <Input name="vnum" label="vnum:" value={vnum} onChange={handleChange} />
          <Input
            name="shortDesc"
            label="Short Desc:"
            value={shortDesc}
            onChange={handleChange}
          />
          <button type="button" onClick={handleReset}>
            Reset
          </button>{" "}
          <button type="submit">Submit</button>
        </form>
      );
    }
    
    // utilizing "PropTypes" to ensure that passed down props match 
    // the definitions below
    Form.propTypes = {
      handleChange: PropTypes.func.isRequired,
      handleReset: PropTypes.func.isRequired,
      handleSubmit: PropTypes.func.isRequired,
      shortDesc: PropTypes.string,
      vnum: PropTypes.string
    };
    
    export default Form;
    

    components/Input/index.js(可重复使用的输入函数)

    import React from "react";
    import PropTypes from "prop-types";
    
    // once again, using object destructuring to pull out the Form's 
    // passed down state and class fields. 
    function Input({ label, name, value, onChange }) {
      return (
        <div name="row">
          <label>
            <b>{label}</b>
            <br />
            <input
              type="text"
              name={name}
              label={label}
              value={value}
              onChange={onChange}
            />
          </label>
        </div>
      );
    }
    
    // utilizing "PropTypes" to ensure that passed down props match 
    // the definitions below
    Input.propTypes = {
      label: PropTypes.string.isRequired,
      name: PropTypes.string.isRequired,
      value: PropTypes.string,
      onChange: PropTypes.func.isRequired
    };
    
    export default Input;
    

    【讨论】:

    • 这是一个非常有用的答案。最终,我对这个项目的目标是获取表单输入并以特定格式将其输出,这可能由新组件处理。我正计划使用 Mob 作为对象来存储来自 MobForm 的数据并传递到这个新组件中,然后该组件将有一个 render() 来格式化我想要的字段。如果你能描述你的解决方案如何适应这个,我很乐意接受你的回答。
    • 有两种方法可以解决这个问题:1.使用redux,它是一个高级状态容器,可以跨组件共享state(我不推荐)或2. Normalize/在输入更改和表单提交时验证字段。下面是规范化输入 onChange 并在 onSubmit 中对其进行验证的示例:stackoverflow.com/questions/55988065/…。对于输入规范化,您可能需要使用单独的 handleChange 函数或构建一个函数来检查哪个字段 name 已更改并相应地进行规范化。
    • 也就是说,如果您只是指在客户端保留这些字段并以某些样式显示值,那么您可以使用localStoragedeveloper.mozilla.org/en-US/docs/Web/API/Window/localStorage
    • 如果你不想使用存储,那么另一种可能性是条件渲染:reactjs.org/docs/conditional-rendering.html。例如,使用stateFormReview 组件之间切换:codesandbox.io/s/simple-form-3k301(填写表单并点击提交)
    • 我已经阅读了您的资料并提出了解决问题的解决方案,正如我用所需的解决方案描述的那样。我很好奇您对解决方案的看法。我已将其发布为答案。
    【解决方案3】:

    在这一行

    <MobForm mob={this.state.mob} onSubmit={() => this.handleSubmit} />
    

    您正在定义一个返回您的 handleSubmit 函数的匿名函数。

    在你的表单中

    <form onSubmit={this.props.onSubmit}>
    

    onSubmit 将执行 this.props.onSubmit,它只返回 handleSubmit 函数,但不会执行它。要修复它,只需将 MobForm 更改为直接传递 handleSubmit,而不是在匿名函数中传递它:

    <MobForm mob={this.state.mob} onSubmit={this.handleSubmit} />
    

    要正确处理提交,您需要将表单输入转换为托管组件。请参阅文档here

    这样的事情会是一个好的开始:

    class MobForm extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                vnum: '',
                shortDesc: '',
            };
    
            this.handleChangeVnum = this.handleChangeVnum.bind(this);
            this.handleChangeShortDesc = this.handleChangeShortDesc.bind(this);
            this.handleSubmit = this.handleSubmit.bind(this);
        }
    
        handleChangeVnum(event) {
            this.setState({vnum: event.target.value});
        }
    
        handleChangeShortDesc(event) {
            this.setState({shortDesc: event.target.value});
        }
    
        handleSubmit(event) {
            this.props.onSubmit(this.state);
            event.preventDefault();
        }
    
        render() {
            return (
                <div>
                    <form onSubmit={this.handleSubmit}>
                        <CreateStringInputField
                            name="vnum"
                            label="vnum:"
                            value={this.state.vnum}
                            onChange={this.handleChangeVnum}
                        />
                        <CreateStringInputField
                            name="shortDesc"
                            label="Short Desc:"
                            value={this.state.shortDesc}
                            onChange={this.handleChangeShortDesc}
                        />
                        <input type="submit" value="Submit" />
                    </form>
                    {console.log(this.state)}
                </div>
            );
        }
    }
    

    并更新CreateStringInputField()

    function CreateStringInputField(props) {
        return (
            <div name="row">
                <label>
                    <b>{props.label}</b>
                    <br />
                    <input
                        type="text"
                        name={props.name}
                        label={props.label}
                        value={props.value}
                        onChange={props.onChange}
                    />
                </label>
            </div>
        );
    }
    

    【讨论】:

    • 这会在alert("A name was submitted: " + this.state.vnum + " event value: " + event.statement.vnum); 行的App.handleSubmit() 中生成错误——错误是Cannot read property 'vnum' of undefined
    • 看看event的值。看起来它没有名为statement 的属性。您还没有在state 上定义vnum
    • 你是对的,事件没有.statement。然而,它似乎也没有任何价值。我已经尝试过 event.vnum、event.name 等。如何在 handleSubmit() 中正确提取这些值?
    • 试试console.log(event)看看onSubmit返回了什么
    • 我怀疑您正在尝试做的事情将需要更多的工作。反应文档解释了如何很好地实现你想要的here,但我认为这超出了你最初问题的范围。
    猜你喜欢
    • 2019-04-02
    • 2019-02-17
    • 2022-07-15
    • 2014-12-18
    • 2019-08-15
    • 1970-01-01
    • 2020-06-21
    • 1970-01-01
    • 2019-03-18
    相关资源
    最近更新 更多