|
| 1 | +--- |
| 2 | +description: "React manages view state through a mechanism called hooks. Brian teaches you how to use them as you build components." |
| 3 | +--- |
| 4 | + |
| 5 | +Okay, so now we want to make it so people can add pizzas to their order. We need a little form that allows them to select the pizza and the size. Create a new file called Order.jsx and add the following: |
| 6 | + |
| 7 | +```javascript |
| 8 | +import Pizza from "./Pizza"; |
| 9 | + |
| 10 | +export default function Order() { |
| 11 | + const pizzaType = "pepperoni"; |
| 12 | + const pizzaSize = "medium"; |
| 13 | + return ( |
| 14 | + <div className="order"> |
| 15 | + <form> |
| 16 | + <div> |
| 17 | + <label htmlFor="pizza-type">Pizza Type</label> |
| 18 | + <select name="pizza-type" value={pizzaType}> |
| 19 | + <option value="pepperoni">The Pepperoni Pizza</option> |
| 20 | + <option value="hawaiian">The Hawaiian Pizza</option> |
| 21 | + <option value="big_meat">The Big Meat Pizza</option> |
| 22 | + </select> |
| 23 | + </div> |
| 24 | + <div> |
| 25 | + <label htmlFor="pizza-size">Pizza Type</label> |
| 26 | + <div> |
| 27 | + <input |
| 28 | + checked={pizzaSize === "small"} |
| 29 | + type="radio" |
| 30 | + name="pizza-size" |
| 31 | + value="small" |
| 32 | + /> |
| 33 | + <label>Small</label> |
| 34 | + <input |
| 35 | + checked={pizzaSize === "medium"} |
| 36 | + type="radio" |
| 37 | + name="pizza-size" |
| 38 | + value="medium" |
| 39 | + /> |
| 40 | + <label>Medium</label> |
| 41 | + <input |
| 42 | + checked={pizzaSize === "large"} |
| 43 | + type="radio" |
| 44 | + name="pizza-size" |
| 45 | + value="large" |
| 46 | + /> |
| 47 | + <label>Large</label> |
| 48 | + </div> |
| 49 | + </div> |
| 50 | + <Pizza |
| 51 | + name="Pepperoni" |
| 52 | + description="Mozzarella Cheese, Pepperoni" |
| 53 | + image="/public/pizzas/pepperoni.webp" |
| 54 | + /> |
| 55 | + <button type="submit">Add to Cart</button> |
| 56 | + </form> |
| 57 | + </div> |
| 58 | + ); |
| 59 | +} |
| 60 | +``` |
| 61 | + |
| 62 | +Now add it to your App.jsx: |
| 63 | + |
| 64 | +```javascript |
| 65 | +// delete Pizza import, and add Order |
| 66 | +import Order from "./Order"; |
| 67 | + |
| 68 | +// in App.jsx, replace all the Pizzas |
| 69 | +<Order />; |
| 70 | +``` |
| 71 | + |
| 72 | +> 🚨 You'll have some errors in the console, that's okay. |
| 73 | +
|
| 74 | +Now navigate to [http://localhost:5173/]() and see that you have two inputs, one for the pizza type and a set of radio buttons for the size. Try and select something with the inputs. You'll see that you can't modify them. Why? Let's think about how React works: when you interact with the inputs, React detects that a DOM event happens. When that happens, React thinks _something_ may have changed so it runs a re-render. Providing your render functions are fast, this is a very quick operation. It then diffs what's currently there and what its render pass came up with. It then updates the minimum amount of DOM necessary. |
| 75 | + |
| 76 | +Notice we're using `className` instead of `class` on the HTML element for CSS classes. This is because `class` is a reserved word in JS and JSX is still just JS. So instead they opted to use `className` which is the [name of the JS API][js-api] for interacting with class names. |
| 77 | + |
| 78 | +Like `className`, `htmlFor` is used because `for` is a reserved word in JS. |
| 79 | + |
| 80 | +So if we type in our input and it re-renders, what gets out in the `select` or radio button tags? Well, its value is tied to `pizzaType` and `pizzaSize` and nothing changed those, so they remain the same. In other words, two way data binding is _not_ free in React. I say this is a feature because it makes you explicit on how you handle your data. Let's go make it work. |
| 81 | + |
| 82 | +```javascript |
| 83 | +// in Order.jsx |
| 84 | +import { useState } from "react"; |
| 85 | + |
| 86 | +// pizzaType and pizzaSize location |
| 87 | +const [pizzaType, setPizzaType] = useState("pepperoni"); |
| 88 | +const [pizzaSize, setPizzaSize] = useState("medium"); |
| 89 | + |
| 90 | +// replace input |
| 91 | +<select |
| 92 | + onChange={(e) => setPizzaType(e.target.value)} |
| 93 | + name="pizza-type" |
| 94 | + value={pizzaType} |
| 95 | +> |
| 96 | + […] |
| 97 | +</select>; |
| 98 | + |
| 99 | +// replace the div surrounding the radio buttons |
| 100 | +<div onChange={(e) => setPizzaSize(e.target.value)}>[…]</div>; |
| 101 | +``` |
| 102 | + |
| 103 | +- This is called a hook. Other frameworks like Vue have adopted it as well. |
| 104 | +- A hook called such (in my head) because it's a hook that gets caught every time the render function gets called. Because the hooks get called in the same order every single time, they'll always point to the same piece of state. Because of that they can be stateful: you can keep pieces of mutable state using hooks and then modify them later using their provided updater functions. |
| 105 | +- An _absolutely key_ concept for you to grasp is hooks rely on this strict ordering. As such, **do not put hooks inside if statements or loops**. If you do, you'll have insane bugs that involve `useState` returning _the wrong state_. If you see `useState` returning the wrong piece of state, this is likely what you did. Every hook must run every time in the same order. |
| 106 | +- The argument given to `useState` is the default value. In our case, we could give it `""` as our default value to make the user have to select something first but in our case we want to default to pepperoni pizza and medium size. |
| 107 | +- `useState` returns to us an array with two things in it: the current value of that state and a function to update that state. We're using a feature of JavaScript called destructuring to get both of those things out of the array. |
| 108 | +- We use the `setPizzaType` / `setPizzaSize` function in the `onChange` attribute of the input. Every time the input is typed into, it's going to call that functions which call `setPizzaType` and `setPizzaSize` with what has been typed into the input or what has been selected or what has been clicked on. When `setPizzaType` and `setPizzaSize` are called, React knows that its state has been modified and kicks off a re-render. |
| 109 | +- You can make your own custom hooks; `useState` is just one of many. |
| 110 | +- Historically, React has been written using `class`es with state being on the instance of the component. This is still a supported pattern in React. We'll see how to do it later. |
| 111 | +- We could have put an onChange handler on each of the radio buttons. However event bubbling works the same in React as it does in the normal DOM and we could put it directly on the div that encapsulates all the radio buttons and just have to do it once. |
| 112 | +- You can use `useState` as many times as you need for various pieces of state! Again, this is why ordering is important because React relies on `useState` to be called in strictly the same order every time so it can give you the same piece of state. |
| 113 | +- Similar to above. We're using `onChange` and `onBlur` because it makes it more accessible. |
| 114 | + |
| 115 | +> I'm showing you how to do a "controlled form" in that we're using hooks to control each part of the form. In reality, it'd be better to leave these _uncontrolled_ (aka don't set the value) and wrap the whole thing in a form. Then we can listen for submit events and use that event to gather info off the form. This is less code and less burdensome to write. If you have a standard form sort of thing to write, do that as an uncontrolled form. If you need to do dynamic validation, react to a user typing a la typeahead (functionality that provides real-time suggestions), or something of the ilk, then a controlled input is perfect, otherwise stick to uncontrolled. |
| 116 | +> Also what's new in React is called a "form action" that is considered unstable. In the future you will just add `<form action="blah">[…]</form>` and a form action will handle the entire form for you. This will then dovetai |
| 117 | +
|
| 118 | +> 🏁 [Click here to see the state of the project up until now: 04-hooks][step] |
| 119 | +
|
| 120 | +[babel]: https://babeljs.io/ |
| 121 | +[step]: https://github.com/btholt/citr-v8-project/tree/master/04-hooks |
| 122 | +[js-api]: https://developer.mozilla.org/en-US/docs/Web/API/Element/className |
0 commit comments