如何从子组件更新父状态数组?

时间:2019-01-08 17:06:18

标签: reactjs react-native ecmascript-6

App
|
|----|---------------------|
     |                     |
     RadioInputForm        TextInputForm

我正在尝试使用子组件中的数据更新父状态下的数组,但事件未触发。我已经尝试过以前的解决方案,例如{()=> callback()}和{()=> callback.bind(this)},但似乎没有任何作用。

在输入指定索引处的值后,应更新父状态下的数组。它的工作方式类似于调查Web应用程序,您应该能够返回到上一个条目,因此可以返回数组。

这是我到目前为止尝试过的,将不胜感激:

// App.js
constructor(props) {
    super(props);

    let radioQuestions = [], textQuestions = [];
    let option = {id: "", options: {option1: false, option2: false, option3: false}, text: ""};

    for(var i = 0; i < data.questions.length; ++i) {
        textQuestions[i] = "";

        if(data.questions[i].question_type === "RadioQuestion") {
            radioQuestions[i] = option;
            radioQuestions[i].id = data.questions[i].id;
        } else radioQuestions[i] = {};
    }

    this.state = {
        textQuestions: textQuestions,
        radioQuestions: radioQuestions,
        index: 0
    };

    this.handleRadioSelect= this.handleRadioSelect.bind(this);
    this.handleRadioTextChange = this.handleRadioTextChange.bind(this);
    this.handleTextQuestionChange= this.handleTextQuestionChange.bind(this);
    this.handleBack = this.handleBack.bind(this);
    this.handleNext = this.handleNext.bind(this);
}

handleRadioSelect(e) {
    const { value } = e.target;
    const questions = this.state.radioQuestions.slice();
    const index = this.state.index;

    switch(value) {
        case "option1":
            if(questions[index].options.option1 === true)
                questions[index].options.option1 = false;
            else questions[index].options.option1 = true;
            break;
        case "option2":
            if(questions[index].options.option2 === true)
                questions[index].options.option2 = false;
            else questions[index].options.option2 = true;
            break;
        case "option3":
            if(questions[index].options.option3 === true)
                questions[index].options.option3 = false;
            else questions[index].options.option3 = true;
            break;
        default:
    }

    this.setState({
        radioQuestions: questions
    });
}

handleRadioTextChange(e) {
    const { value } = e.target;

    const questions = this.state.radioQuestions.slice()
    questions[this.state.index].text = value;

    this.setState({
        radioQuestions: questions
    })
}

handleTextQuestionChange(e) {
    const { value } = e.target;

    const answers = this.state.textAnswers.slice()
    answers[this.props.index] = value;

    this.setState({
        textAnswers: answers
    })
}

具有Radio形式的子组件:

constructor(props) {
    super(props);

    this.state = {
        radioQuestions: this.props.radioQuestions
    }

    this.handleRadioTextChange = this.handleRadioTextChange.bind(this);
    this.handleRadioSelect = this.handleRadioSelect.bind(this);
}

handleRadioSelect(e) {
    const { value } = e.target;
    const questions = this.state.radioQuestions.slice();
    const index = this.state.index;

    console.log(JSON.stringify(questions[index]));

    switch(value) {
        case "option1":
            if(questions[index].options.option1 === true)
                questions[index].options.option1 = false;
            else questions[index].options.option1 = true;
            break;
        case "option2":
            if(questions[index].options.option2 === true)
                questions[index].options.option2 = false;
            else questions[index].options.option2 = true;
            break;
        case "option3":
            if(questions[index].options.option3 === true)
                questions[index].options.option3 = false;
            else questions[index].options.option3 = true;
            break;
        default:
    }

    this.props.handleRadioSelect(e);

    this.setState({
        radioQuestions: questions
    });
}

handleRadioTextChange(e) {
    const { value } = e.target;

    const questions = this.state.radioQuestions.slice()
    questions[this.props.index].text = value;

    this.setState({
        radioQuestions: questions
    })
}

render() {
    return (
        <div className="form-group">
            <input type="radio" name="option1"
                  value="option1"
                  onChange = { this.state.handleRadioSelect }
                  defaultChecked={ this.state.radioQuestions[this.props.index].options.option1 } /> Option 1<br />
            <input type="radio" name="option2"
                  value="option2"
                  onChange = { this.state.handleRadioSelect }
                  defaultChecked={ this.state.radioQuestions[this.props.index].options.option2 } /> Option 2<br />
            <input type="radio" name="option3"
                  value="option3"
                  onChange = { this.state.handleRadioSelect }
                  defaultChecked={ this.state.radioQuestions[this.props.index].options.option3 } /> Option 3<br />
            <textarea rows="1" cols="40" id="answer" name="answer"
                className ="form-control input-default"
                onChange={ this.state.handleRadioTextChange }
                defaultValue = { this.state.radioQuestions[this.props.index].text } />
        </div>
    );
}

重复的代码(在父级和子级中)是我为解决此问题而进行的尝试。最后是“文本输入”表单组件:

constructor(props) {
    super(props);

    this.state = {
        textAnswers: this.props.textQuestions,
    };

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

handleTextQuestionChange(e) {
    const { value } = e.target;

    const answers = this.state.textAnswers.slice()
    answers[this.props.index] = value;

    this.props.handleTextQuestionChange(e);

    this.setState({
        textAnswers: answers
    })
}

render() {
    return (
        <div className="form-group">
            <textarea rows="4" cols="50" id="answer" name="answer"
                className ="form-control input-default" onChange = { this.state.handleTextQuestionChange  }
                defaultValue = { this.state.textAnswers[this.props.index] } />
        </div>
    );
}

如果我不遵循良好做法,也将感谢您提供任何其他建议,谢谢。

2 个答案:

答案 0 :(得分:2)

简短答案和有效的代码和框:https://codesandbox.io/s/21q3j32qyy 为了了解问题出在哪里,我重构了很多东西并删除了重复的代码。

下面有很长的答案...

首先,问题概述

我们有一个对象,该对象应该来自API或包含用户问题列表以及一个或一个或多个其他信息(如调查标题)的对象。

现在,可以在App.js中通过以下方式检索示例数据:

var data = require("./payload.json");

合法的,即使使用ES6,也可以这样写(为了与代码的其余部分保持一致):

import data from "./payload.json"

通过查看payload.json文件,可以看到存在3种可能的问题类型:

  1. 具有3个选项,其行为类似于复选框(即使在您的代码中是单选按钮)和文本;
  2. 只有文字;
  3. 使用图片上传器;

问题还包含要提示用户的文本,ID和区分它们的类型。

用户还应该能够浏览他/她的答案。

目标

在我看来,您基本上...需要使用此表格来保持用户的所有状态(我想您应该将它们发送到API一段时间)。

App.js

这是应用程序或至少是示例的主要组件。

在这里我们应该:

  1. 接受/处理从某处获取的问题;
  2. 呈现当前问题;
  3. 能够浏览问题;
  4. 处理用户的答案;

在构造函数中,您所做的事情对我来说似乎很奇怪(当然,如果出于某种原因它是正确的,请告诉我):您遍历数据变量中的每个问题并创建两个单独的数组,其中一个用于文本问题一个用于广播问题,当问题属于另一种类型时,保留两个假元素...

相反,我想创建一个空答案的唯一数组,每种问题的字段都不同。因此,我创建了一个Answers.js文件来处理一些事情。

首先,我有一个常量,用于保存各种类型的问题的字段(及其默认值):

// Answers.js    

const RADIO_FIELDS = {
  options: {
    option1: false,
    option2: false,
    option3: false
  }
};

const TEXT_FIELDS = {
  text: ""
};

const UPLOAD_FIELDS = {
  file: "",
  imageUrl: ""
};

我还创建了另一个文件Question.js,其中定义了一些实用程序常量来处理数据中定义的问题类型:

// Question.js

export const RADIO_QUESTION_TYPE = "RadioQuestion";
export const TEXT_QUESTION_TYPE = "TextQuestion";
export const UPLOAD_QUESTION_TYPE = "FileUpload";

export const QuestionTypes = {
  RADIO: RADIO_QUESTION_TYPE,
  TEXT: TEXT_QUESTION_TYPE,
  UPLOAD: UPLOAD_QUESTION_TYPE
};

Answers.js文件中建立实用程序,我创建了一个方法,该方法从问题开始返回“ answer template”普通对象。为了获得结果,我只需要检查问题类型,然后将一些常见字段与问题类型的专用字段合并即可:

// Answers.js

const fromQuestion = question => {
  const baseAnswer = {
    id: question.id,
    type: question.question_type
  };

  switch (question.question_type) {
    case QuestionTypes.RADIO:
      return Object.assign({}, baseAnswer, RADIO_FIELDS, TEXT_FIELDS);
    case QuestionTypes.TEXT:
      return Object.assign({}, baseAnswer, TEXT_FIELDS);
    case QuestionTypes.UPLOAD:
      return Object.assign({}, baseAnswer, UPLOAD_FIELDS);
    default:
      return null;
  }
};

在这里,我决定处理未知类型的问题:在开关的默认情况下返回null将过滤掉这些问题。

同样,我编写了一个超级小型实用程序方法来执行此检查:

// Answers.js

const isValid = answer => answer !== null;

最后,一个方法将所有问题映射到所有(有效)“答案模板”,然后将其像普通对象一样导出(只是因为我喜欢我们将在{{1}中获得的语​​法) }组件(使用此方法时):

App.js

这时,我们可以在App.js中导入Answers.js并通过以下方式设置状态变量:

// Answers.js

const from = questions =>
  (questions || []).map(fromQuestion).filter(isValid);

export default {
  from
};

我们将在// App.js ... import Answers from './Answers'; export default class App extends React.Component { state = { answers: Answers.from(data.questions), index: 0 }; ... 属性中拥有该对象:

answers

好的,我们拥有数据结构和一种从原始数据中获取数据的干净方法。

现在我们必须正确地提出一个问题。在您的代码中会发生以下情况:

[
  {
    id: 2501,
    type: "RadioQuestion",
    options: {
      option1: false,
      option2: false,
      option3: false
    },
    text: ""
  }, {
    id: 2447,
    type: "TextQuestion",
    text: ""
  }, {
  ...

您正在执行一种方法,该方法生成一些要呈现的内容……只不过是组件!

因此,为了以React的方式进行操作,在// App.js, render method ... render() { const question = () => { switch (data.questions[this.state.index].question_type) { case "RadioQuestion": return ( <RadioQuestion radioQuestion={this.state.radioQuestions[this.state.index]} handleRadio={this.handleRadio} /> ); case "TextQuestion": return ( <TextQuestion textQuestion={this.state.textQuestions[this.state.index]} handleText={this.handleText} /> ); case "FileUpload": return <FileUpload index={this.state.index} />; default: return <h2>Unknown Question Format</h2>; } }; return ( ... </label> { question() } <div className="form-group input-margin-top"> ... 文件中,我定义了一个类似于您的Question组件:

Question.js

我分解了prop类型,并渲染了一个与其他props相同的组件。

继续进行编辑(恐怕会丢失所有内容!)

答案 1 :(得分:0)

我认为这里的主要问题是您的状态是持有一个包含对象的数组,这些对象无法使用Array.slice函数进行克隆。请注意,此函数只是对数组进行浅表复制,以使其中的对象在状态转换之间保持不变。

我建议使用ImmutableJS处理深层物体的状态突变。