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.