10. Use Forms To Add An Item

10.1. Add The Form

To be able to add FAQ items to the list we will start by adding an add form:

31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
render() {
  return (
    <div>
      <ul>
        {this.state.faq.map((item, index) => (
          <FaqItem
            question={item.question}
            answer={item.answer}
            index={index}
            onDelete={this.onDelete}
          />
        ))}
      </ul>
      <form>
        <label>
          Question: <input name="question" type="text" />
        </label>
        <label>
          Answer: <textarea name="answer" />
        </label>
        <input type="submit" value="Add" />
      </form>
    </div>
  );
}

Differences

--- a/src/App.js
+++ b/src/App.js
@@ -30,16 +30,27 @@ class App extends Component {

  render() {
    return (
-      <ul>
-        {this.state.faq.map((item, index) => (
-          <FaqItem
-            question={item.question}
-            answer={item.answer}
-            index={index}
-            onDelete={this.onDelete}
-          />
-        ))}
-      </ul>
+      <div>
+        <ul>
+          {this.state.faq.map((item, index) => (
+            <FaqItem
+              question={item.question}
+              answer={item.answer}
+              index={index}
+              onDelete={this.onDelete}
+            />
+          ))}
+        </ul>
+        <form>
+          <label>
+            Question: <input name="question" type="text" />
+          </label>
+          <label>
+            Answer: <textarea name="answer" />
+          </label>
+          <input type="submit" value="Add" />
+        </form>
+      </div>
    );
  }
}

10.2. Manage Field Values In The State

To manage the values of the fields in the form we will use the state. Add a question and answer value to the state which contains the values of the inputs. Add onChange handlers to the input and textarea which will change the values in the state when the input changes. This pattern is called controlled inputs.

Solution

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import React, { Component } from "react";
import FaqItem from "./components/FaqItem";
import "./App.css";

class App extends Component {
  constructor(props) {
    super(props);
    this.onDelete = this.onDelete.bind(this);
    this.onChangeQuestion = this.onChangeQuestion.bind(this);
    this.onChangeAnswer = this.onChangeAnswer.bind(this);
    this.state = {
      faq: [
        {
          question: "What does the Plone Foundation do?",
          answer:
            "The mission of the Plone Foundation is to protect and promote Plone. The Foundation provides marketing assistance, awareness, and evangelism assistance to the Plone community. The Foundation also assists with development funding and coordination of funding for large feature implementations. In this way, our role is similar to the role of the Apache Software Foundation and its relationship with the Apache Project."
        },
        {
          question: "Why does Plone need a Foundation?",
          answer:
            "Plone has reached critical mass, with enterprise implementations and worldwide usage. The Foundation is able to speak for Plone, and provide strong and consistent advocacy for both the project and the community. The Plone Foundation also helps ensure a level playing field, to preserve what is good about Plone as new participants arrive."
        }
      ],
      question: "",
      answer: ""
    };
  }

  onDelete(index) {
    let faq = this.state.faq;
    faq.splice(index, 1);
    this.setState({
      faq
    });
  }

  onChangeQuestion(event) {
    this.setState({
      question: event.target.value
    });
  }

  onChangeAnswer(event) {
    this.setState({
      answer: event.target.value
    });
  }

  render() {
    return (
      <div>
        <ul>
          {this.state.faq.map((item, index) => (
            <FaqItem
              question={item.question}
              answer={item.answer}
              index={index}
              onDelete={this.onDelete}
            />
          ))}
        </ul>
        <form>
          <label>
            Question:
            <input
              name="question"
              type="text"
              value={this.state.question}
              onChange={this.onChangeQuestion}
            />
          </label>
          <label>
            Answer:
            <textarea
              name="answer"
              value={this.state.answer}
              onChange={this.onChangeAnswer}
            />
          </label>
          <input type="submit" value="Add" />
        </form>
      </div>
    );
  }
}

export default App;
--- a/src/App.js
+++ b/src/App.js
@@ -6,17 +6,23 @@ class App extends Component {
  constructor(props) {
    super(props);
    this.onDelete = this.onDelete.bind(this);
+    this.onChangeQuestion = this.onChangeQuestion.bind(this);
+    this.onChangeAnswer = this.onChangeAnswer.bind(this);
    this.state = {
      faq: [
        {
          question: "What does the Plone Foundation do?",
-          answer: "The mission of the Plone Foundation is to protect and..."
+          answer:
+            "The mission of the Plone Foundation is to protect and promote Plone. The Foundation provides marketing assistance, awareness, and evangelism assistance to the Plone community. The Foundation also assists with development funding and coordination of funding for large feature implementations. In this way, our role is similar to the role of the Apache Software Foundation and its relationship with the Apache Project."
        },
        {
          question: "Why does Plone need a Foundation?",
-          answer: "Plone has reached critical mass, with enterprise..."
+          answer:
+            "Plone has reached critical mass, with enterprise implementations and worldwide usage. The Foundation is able to speak for Plone, and provide strong and consistent advocacy for both the project and the community. The Plone Foundation also helps ensure a level playing field, to preserve what is good about Plone as new participants arrive."
        }
-      ]
+      ],
+      question: "",
+      answer: ""
    };
  }

@@ -28,6 +34,18 @@ class App extends Component {
    });
  }

+  onChangeQuestion(event) {
+    this.setState({
+      question: event.target.value
+    });
+  }
+
+  onChangeAnswer(event) {
+    this.setState({
+      answer: event.target.value
+    });
+  }
+
  render() {
    return (
      <div>
@@ -43,10 +61,21 @@ class App extends Component {
        </ul>
        <form>
          <label>
-            Question: <input name="question" type="text" />
+            Question:
+            <input
+              name="question"
+              type="text"
+              value={this.state.question}
+              onChange={this.onChangeQuestion}
+            />
          </label>
          <label>
-            Answer: <textarea name="answer" />
+            Answer:
+            <textarea
+              name="answer"
+              value={this.state.answer}
+              onChange={this.onChangeAnswer}
+            />
          </label>
          <input type="submit" value="Add" />
        </form>

10.3. Submit Handler

Now that our values are managed in the state we can write our submit handler. Write an onSubmit handler which reads the values from the state and add the new FAQ item to the list. After the item is added the inputs should also reset to empty values.

Solution

Make sure you bind the onSubmit handler.

11
this.onSubmit = this.onSubmit.bind(this);

And add this to the body of the class.

 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
onSubmit(event) {
  this.setState({
    faq: [
      ...this.state.faq,
      {
        question: this.state.question,
        answer: this.state.answer
      }
    ],
    question: "",
    answer: ""
  });
  event.preventDefault();
}

render() {
  return (
    <div>
      <ul>
        {this.state.faq.map((item, index) => (
          <FaqItem
            question={item.question}
            answer={item.answer}
            index={index}
            onDelete={this.onDelete}
          />
        ))}
      </ul>
      <form onSubmit={this.onSubmit}>
        <label>
          Question:
          <input
            name="question"
            type="text"
            value={this.state.question}
            onChange={this.onChangeQuestion}
          />
        </label>
        <label>
          Answer:
          <textarea
            name="answer"
            value={this.state.answer}
            onChange={this.onChangeAnswer}
          />
        </label>
        <input type="submit" value="Add" />
      </form>
    </div>
  );
}
--- a/src/App.js
+++ b/src/App.js
@@ -8,6 +8,7 @@ class App extends Component {
    this.onDelete = this.onDelete.bind(this);
    this.onChangeQuestion = this.onChangeQuestion.bind(this);
    this.onChangeAnswer = this.onChangeAnswer.bind(this);
+    this.onSubmit = this.onSubmit.bind(this);
    this.state = {
      faq: [
        {
@@ -46,6 +47,21 @@ class App extends Component {
    });
  }

+  onSubmit(event) {
+    this.setState({
+      faq: [
+        ...this.state.faq,
+        {
+          question: this.state.question,
+          answer: this.state.answer
+        }
+      ],
+      question: "",
+      answer: ""
+    });
+    event.preventDefault();
+  }
+
  render() {
    return (
      <div>
@@ -59,7 +75,7 @@ class App extends Component {
            />
          ))}
        </ul>
-        <form>
+        <form onSubmit={this.onSubmit}>
          <label>
            Question:
            <input