30 Days Of JavaScript

Day 18: Forms

Lesson 4: Async Forms

Saving the data from a form to a server is the most common thing we do, let's do that.

Let's reset out code to the end of lesson 3:

1<!DOCTYPE html>
2<html lang="en">
3  <head>
4    <meta charset="UTF-8" />
5    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7    <title>Document</title>
8
9    <style>
10      body {
11        font-family: "Open Sans", "Helvetica Neue", sans-serif;
12      }
13
14      #container {
15        width: 800px;
16        margin: auto;
17      }
18
19      h1 {
20        text-align: center;
21      }
22
23      ul {
24        font-size: 20px;
25        line-height: 35px;
26        padding: 0;
27      }
28
29      form {
30        font-size: 18px;
31      }
32
33      button {
34        padding: 5px;
35        min-width: 80px;
36      }
37
38      input {
39        padding: 5px;
40        border-radius: 5px;
41        border: 1px solid gray;
42      }
43
44      li {
45        list-style-type: none;
46      }
47
48      #red-text {
49        color: red;
50      }
51
52      #blue-text {
53        color: blue;
54      }
55
56      .title-large {
57        font-size: 48px;
58        font-weight: bold;
59      }
60
61      .title-small {
62        font-size: 24px;
63        font-weight: bold;
64      }
65
66      .hidden {
67        display: none;
68      }
69
70      .warning {
71        color: orangered;
72      }
73    </style>
74  </head>
75  <body>
76    <div id="container">
77      <h1>The Broom Shop Stock</h1>
78      <ul id="stock-list"></ul>
79      <form id="stock-form">
80        <label for="name">Broom Name</label>
81        <input type="text" name="name" />
82        <label for="price">Price</label>
83        <input type="number" name="price" required />
84        <label for="quantity">Quantity</label>
85        <input type="number" name="quantity" required />
86        <button type="submit" id="form-submit" disabled>Add</button>
87      </form>
88    </div>
89  </body>
90  <script>
91    const createRow = (broom) => {
92      const li = document.createElement("li");
93      const name = document.createElement("span");
94      name.innerText = broom.name;
95      const price = document.createElement("span");
96      price.innerText = broom.price;
97      const quantity = document.createElement("span");
98      quantity.innerText = broom.quantity;
99
100      const currencySpacer = document.createTextNode(": $");
101      const quantitySpacer = document.createTextNode(" x ");
102
103      li.appendChild(name);
104      li.appendChild(currencySpacer);
105      li.appendChild(price);
106      li.appendChild(quantitySpacer);
107      li.appendChild(quantity);
108
109      return li;
110    };
111
112    const stock = [
113      { name: "Nimbus 2000", price: 1000, quantity: 8 },
114      { name: "Air Wave Gold", price: 50, quantity: 6 },
115    ];
116
117    const ul = document.getElementById("stock-list");
118
119    stock.forEach((broom) => {
120      const newLi = createRow(broom);
121      ul.appendChild(newLi);
122    });
123
124    const handleSubmit = (e) => {
125      e.preventDefault();
126
127      const formData = new FormData(e.target);
128      const formProps = Object.fromEntries(formData);
129
130      const newLi = createRow(formProps);
131      ul.appendChild(newLi);
132      e.target.reset();
133    };
134
135    const form = document.getElementById("stock-form");
136    form.onsubmit = handleSubmit;
137
138    const handleOnChange = (e) => {
139      const formData = new FormData(e.currentTarget);
140      const formProps = Object.fromEntries(formData);
141
142      let nameValid = false;
143
144      if (
145        formProps.name.toLowerCase().includes("nimbus") ||
146        formProps.name.toLowerCase().includes("comet")
147      ) {
148        nameValid = true;
149      }
150
151      const priceValid = !isNaN(parseInt(formProps.price));
152      const quantityValid = !isNaN(parseInt(formProps.quantity));
153
154      if (nameValid && priceValid && quantityValid) {
155        document.getElementById("form-submit").disabled = false;
156      } else {
157        document.getElementById("form-submit").disabled = true;
158      }
159    };
160
161    form.oninput = handleOnChange;
162  </script>
163</html>

We will use a service called https://reqres.in/ to send the data. Whatever we send it, it will respond with so it's a nice way to show how you handle network requests without having to build a back end and server.

Here is your new handleSubmit function.

1const handleSubmit = (e) => {
2  e.preventDefault();
3
4  const formData = new FormData(e.target);
5  const formProps = Object.fromEntries(formData);
6
7  fetch("https://reqres.in/api/brooms", {
8    method: "POST",
9    headers: {
10      "Content-Type": "application/json",
11    },
12    body: JSON.stringify(formProps),
13  })
14    .then((res) => res.json())
15    .then((data) => {
16      console.log(data);
17      const newLi = createRow(data);
18      ul.appendChild(newLi);
19      e.target.reset();
20    })
21    .catch((err) => {
22      alter("Whoops, something went wrong");
23      console.error(err.message);
24    });
25};

You should recognize the double promise of a fetch request now. But we have some extra information at the top. To send data to a server we use a POST request, so we need to pass a second parameter to fetch() after the url with the options for the request and the data we want to send. This options object says we will be using the POST method, that the content type is application/json and that the data we are sending is formProps.

Let's look at that data for a second. Then we add it to a property called body and we turn the JSON object into a string using JSON.stringify(). You see everything end over the internet is done so as plain text, the internet has no idea what a number or a JSON object are. Just plain text. So we need to take out object and turn it in to a string.

We then get the response as res, get the data from that with .json() and then turn that data into an <li> like before.

Now this happens so fast there is a chance you won't believe me, well check your logs and you will see that the line console.log(data) looks something like this:

1{name: 'comet', price: '777', quantity: '7', id: '354', createdAt: '2022-11-25T14:59:32.168Z'}

The id and createdAt properties we added by the server for us .... probably to prove that something actually happened!

Let's assume our users have slow internet and we don't want them submitting the form again until we get a response. To do that we would disable the button right before calling fetch and then reenable it in a .finally(). Between the code you have at the moment and the try/catch/finally lesson you have everything you need to try this yourself.

Outline

Go Pro?

If you upgraded to pro, sign in here

  • About
  • Blog
  • Privacy
Looking to email me? You can get me on my first name at allthecode.co