|
| 1 | +# React Part 4 - Full Apps using `create-react-app` (CRA) |
| 2 | + |
| 3 | +## Projected Time |
| 4 | + |
| 5 | +- 6 hours |
| 6 | + |
| 7 | +### Prerequisites |
| 8 | + |
| 9 | +- [React Part 3 - Components & Hierarchies](./react-part-3-component-hierarchies.md) |
| 10 | + |
| 11 | +### Motivation |
| 12 | + |
| 13 | +Until now, we've been using simple React development setups good for learning. But real apps usually need a bit more features available, so for your eventual final project you'll want to use `create-react-app` (which we'll call CRA in this lesson), a tool that generates a template empty React project so you can just starting writing your own code and not worry about how to setup all the moving parts. |
| 14 | + |
| 15 | +#### Benefits |
| 16 | + |
| 17 | +- **👀 Live reload!** When you save a change to a file, it will almost instantly update in your browser. No more need to hit Cmd+R to reload the page |
| 18 | +- You can organize your React components and CSS any way you want and consistently use `import` to refer to them (no more confusion about `require` vs `import`) |
| 19 | +- Quickly add npm packages using `yarn add` |
| 20 | +- Easily deploy to the cloud. You can use the `build` command to package all your code up |
| 21 | +- Lots of help available on Stack Overflow. Because many users, especially beginner engineers, are using CRA, they have probably encountered the same problem as you and are posting about it! |
| 22 | + |
| 23 | +### Objectives |
| 24 | + |
| 25 | +The important areas you should leave this lesson knowing. |
| 26 | + |
| 27 | +- How does `create-react-app` compare to what we've been using so far? |
| 28 | +- The basic files generated and what each one does, e.g. index.html, App.js, App.css |
| 29 | +- How to add an npm package to an existing CRA app |
| 30 | + |
| 31 | +### Lesson |
| 32 | + |
| 33 | +Its official tagline is: |
| 34 | + |
| 35 | +> Set up a modern web app by running one command |
| 36 | +
|
| 37 | +CRA is an officially supported command-line tool (CLI) that lets you create a new, empty React application that has many add-ons automatically set up for you so you can just begin coding. |
| 38 | + |
| 39 | +```sh |
| 40 | +create-react-app my-fun-app |
| 41 | +``` |
| 42 | + |
| 43 | +Since the main benefit of CRA is that you don't need to worry about how it works, we're mainly going to focus on trying it out through guided practice but there are links below if you want to know more about how it works. |
| 44 | + |
| 45 | +### Common Mistakes & Misconceptions |
| 46 | + |
| 47 | +- CRA is only for toy apps and _real engineers_ setup React from scratch |
| 48 | + - Actually, many professional projects leverage CRA or another starting point template like [Next.js](https://nextjs.org/) when beginning applications. Even if you want to customize the app in some ways, the general setup is usually a good starting point for many types of projects and **it will not make your portfolio project seem amateur to use it** |
| 49 | +- You **must** use CRA to build React apps |
| 50 | + - Not true, but it is strongly recommended you build experience using it first before trying to do it yourself without CRA. Although it's much easier, especially for a beginner, many companies have ofted to create a customized setup so they have full control over all aspects of the application |
| 51 | + |
| 52 | +## Guided Practice |
| 53 | + |
| 54 | +Let's setup a small app. |
| 55 | + |
| 56 | +- `[npx](https://bambielli.com/til/2018-10-06-npx/) create-react-app todo-cra` |
| 57 | +- _grab a coffee while it installs everything_ |
| 58 | +- `cd todo-cra` |
| 59 | +- `yarn start` |
| 60 | + |
| 61 | +The app should open in a new browser tab automatically and show a spinning atom logo. |
| 62 | + |
| 63 | +- `code .` to open the generated project in your editor |
| 64 | +- Find `App.js` in the `src` folder |
| 65 | +- Change the contents to: `<p>Hello Techtonica!</p>` and save |
| 66 | +- The browser should live update automatically 🥳 |
| 67 | + |
| 68 | +### Folder Structure |
| 69 | + |
| 70 | +Review the [existing files](https://create-react-app.dev/docs/folder-structure) of the project. |
| 71 | + |
| 72 | +The most important files are |
| 73 | + |
| 74 | +1. `src/App.css` - all the styles for your React components |
| 75 | +1. `public/index.html` - the non-React content of your web pages, e.g. favicon, static footer, etc |
| 76 | +1. `src/App.js` - mounts your root component |
| 77 | + |
| 78 | +#### `App.css` |
| 79 | + |
| 80 | +Change the color of the "Learn React" link to `#b36ff6` or another color you like on https://colors.lol/. Changes to CSS will live update as well. |
| 81 | + |
| 82 | +#### `index.html` |
| 83 | + |
| 84 | +- Change the page title to "TODO" |
| 85 | +- Find a nice favicon on https://www.flaticon.com/ |
| 86 | +- Make this the new favicon |
| 87 | + |
| 88 | +Now let's add the main application logic to `App.js`. We'll keep everything in one file for now but as your app grows, you probably want to split components to their own files. |
| 89 | + |
| 90 | +### Starter Components |
| 91 | + |
| 92 | +Create a new file. |
| 93 | + |
| 94 | +```jsx |
| 95 | +// Todo.js |
| 96 | +import React from 'react'; |
| 97 | + |
| 98 | +class Todo extends React.Component { |
| 99 | + render() { |
| 100 | + const { todo } = this.props; |
| 101 | + return <div>{todo.text}</div>; |
| 102 | + } |
| 103 | +} |
| 104 | +export default Todo; |
| 105 | +``` |
| 106 | + |
| 107 | +```jsx |
| 108 | +// App.js |
| 109 | +import React from 'react'; |
| 110 | +import Todo from './Todo'; |
| 111 | +import './App.css'; |
| 112 | + |
| 113 | +class Todo extends React.Component { |
| 114 | + render() { |
| 115 | + const { todo } = this.props; |
| 116 | + return <div>{todo.text}</div>; |
| 117 | + } |
| 118 | +} |
| 119 | + |
| 120 | +class App extends React.Component { |
| 121 | + constructor(props) { |
| 122 | + super(props); |
| 123 | + this.state = { |
| 124 | + todos: [{ text: 'Walk dog' }, { text: 'Feed cat' }] |
| 125 | + }; |
| 126 | + } |
| 127 | + render() { |
| 128 | + const { todos } = this.state; |
| 129 | + return ( |
| 130 | + <div className="App"> |
| 131 | + <h1>Todos</h1> |
| 132 | + <div> |
| 133 | + {todos.length && |
| 134 | + todos.map((todo, idx) => <Todo key={idx} todo={todo} />)} |
| 135 | + </div> |
| 136 | + </div> |
| 137 | + ); |
| 138 | + } |
| 139 | +} |
| 140 | + |
| 141 | +export default App; |
| 142 | +``` |
| 143 | + |
| 144 | +You should see the heading and a single todo. |
| 145 | + |
| 146 | +#### Adding isCompleted |
| 147 | + |
| 148 | +Since this is a task app we want to mark tasks as complete. So let's change the shape of the todo data to include an `isCompleted` boolean. |
| 149 | + |
| 150 | +```js |
| 151 | +state = { |
| 152 | + todos: [ |
| 153 | + { text: 'Walk dog', isCompleted: false }, |
| 154 | + { text: 'Feed cat', isCompleted: false } |
| 155 | + ] |
| 156 | +}; |
| 157 | +``` |
| 158 | + |
| 159 | +Now let's update the `<Todo/>` component to render `isCompleted` as a checkbox. For attributes see [MDN Checkbox](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox). |
| 160 | + |
| 161 | +```jsx |
| 162 | +class Todo extends React.Component { |
| 163 | + render() { |
| 164 | + const { todo } = this.props; |
| 165 | + const { text, isCompleted } = todo; |
| 166 | + return ( |
| 167 | + <div> |
| 168 | + <input type="checkbox" checked={isCompleted}></input> |
| 169 | + {text} |
| 170 | + </div> |
| 171 | + ); |
| 172 | + } |
| 173 | +} |
| 174 | +``` |
| 175 | + |
| 176 | +Manually edit the data and add some completed and incompleted tasks to test. |
| 177 | + |
| 178 | +#### Checkbox |
| 179 | + |
| 180 | +Now let's allow the user to change `isCompleted` by checking the checkbox. In React, input elements have an `onChange` prop that accepts a function called when the user modifies the input. See [handling events](https://reactjs.org/docs/handling-events.html) in the official docs. |
| 181 | + |
| 182 | +```jsx |
| 183 | +class Todo extends React.Component { |
| 184 | + toggleCompletion(todo) { |
| 185 | + todo.isCompleted = !todo.isCompleted; |
| 186 | + console.log('todo =>', todo); |
| 187 | + } |
| 188 | + |
| 189 | + render() { |
| 190 | + const { todo } = this.props; |
| 191 | + const { text, isCompleted } = todo; |
| 192 | + return ( |
| 193 | + <div> |
| 194 | + <input |
| 195 | + type="checkbox" |
| 196 | + checked={isCompleted} |
| 197 | + onChange={this.toggleCompletion.bind(this, todo)} |
| 198 | + ></input> |
| 199 | + {text} |
| 200 | + </div> |
| 201 | + ); |
| 202 | + } |
| 203 | +} |
| 204 | +``` |
| 205 | + |
| 206 | +Click the checkbox a few times. Nothing seems to happen. But in the log the code is working: |
| 207 | + |
| 208 | +``` |
| 209 | +todo => Object { text: "Walk dog", isCompleted: true } |
| 210 | +todo => Object { text: "Walk dog", isCompleted: false } |
| 211 | +todo => Object { text: "Walk dog", isCompleted: true } |
| 212 | +``` |
| 213 | + |
| 214 | +This is because, as you probably remember, **React only re-renders when state changes**. It assumes props are read-only and do not change, even though as the above shows, you can modify them since JavaScript doesn't stop you. So we need to change the state, but state lives one level above `Todo` in `App`. Like we learned in Part 3, passing functions down to child components is a good strategy for handling this situation. |
| 215 | + |
| 216 | +#### Create an Updater Function in App |
| 217 | + |
| 218 | +```jsx |
| 219 | +// New method |
| 220 | +updateTodo(todo, changes) { |
| 221 | + this.setState({ |
| 222 | + todos: this.state.todos.map((existing) => { |
| 223 | + if (todo === existing) { |
| 224 | + return { ...existing, ...changes }; |
| 225 | + } |
| 226 | + return existing; |
| 227 | + }), |
| 228 | + }); |
| 229 | +} |
| 230 | +// ... |
| 231 | +// pass the method in as a prop, bound to this so it will work |
| 232 | +todos.map((todo, idx) => ( |
| 233 | + <Todo |
| 234 | + key={idx} |
| 235 | + todo={todo} |
| 236 | + updateTodo={this.updateTodo.bind(this)} |
| 237 | + /> |
| 238 | + )) |
| 239 | +``` |
| 240 | + |
| 241 | +This code is a little more complicated than you might've expected. This is because it's creating a whole new copy of the array (using `.map`) rather than finding the item and changing its data. |
| 242 | + |
| 243 | +#### Rewrite `updateTodo` in your own coding style |
| 244 | + |
| 245 | +You'll learn a lot more about different ways of managing state in React, but that is a whole big topic in itself. For now, you should rewrite the `updateTodo` function in a way that makes sense for you. There are many ways to accomplish the same logic. |
| 246 | + |
| 247 | +Now test out clicking the checkbox. It should toggle correctly as expected. |
| 248 | + |
| 249 | +### Independent Practice |
| 250 | + |
| 251 | +With your daily pair, review each other's code from above and see if you can explain how each part is working, especially how you chose to rewrite the `updateTodo` function. Add some `console.log` statements to verify your understanding. |
| 252 | + |
| 253 | +Next, pair program to add a feature: sorting. |
| 254 | + |
| 255 | +#### Sorting |
| 256 | + |
| 257 | +First we need something to sort by so let's add a `createdAt` property to each item that will be a `Date` object. |
| 258 | + |
| 259 | +```js |
| 260 | +{ |
| 261 | + text: "Walk dog", |
| 262 | + isCompleted: false, |
| 263 | + createdAt: new Date(), |
| 264 | +}, |
| 265 | +``` |
| 266 | + |
| 267 | +Let's support two sort options: newest first or oldest first. |
| 268 | + |
| 269 | +Let's make newest first the default and add it to our starting state. |
| 270 | + |
| 271 | +```js |
| 272 | +state = { |
| 273 | + sort: 'oldest', |
| 274 | + todos: [] |
| 275 | +}; |
| 276 | +``` |
| 277 | + |
| 278 | +Then based on that value, we'll sort `state.todos`. |
| 279 | + |
| 280 | +```js |
| 281 | +const { todos, sort } = this.state; |
| 282 | +if (sort === 'oldest first') { |
| 283 | + todos.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()); |
| 284 | +} |
| 285 | +``` |
| 286 | + |
| 287 | +Check the browser and make sure it's sorting correctly. You may have to give the todos specific dates to test it, e.g. `new Date("2021-01-15 00:00:00-0800")` |
| 288 | + |
| 289 | +Now add another option to sort by `'newest'`. |
| 290 | + |
| 291 | +```jsx |
| 292 | +if (sort === 'oldest') { |
| 293 | + todos.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()); |
| 294 | +} else if (sort === 'newest') { |
| 295 | + todos.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); |
| 296 | +} |
| 297 | +``` |
| 298 | + |
| 299 | +Edit the starting state and make sure the sorting works as expected. |
| 300 | + |
| 301 | +#### Sorting Toggle |
| 302 | + |
| 303 | +Now add a button that switches the sorting from one option to the other. |
| 304 | + |
| 305 | +```jsx |
| 306 | +<div> |
| 307 | + <button |
| 308 | + onClick={() => |
| 309 | + this.setState({ sort: sort === 'oldest' ? 'newest' : 'oldest' }) |
| 310 | + } |
| 311 | + > |
| 312 | + ⬇️ Sort by {sort === 'oldest' ? 'newest' : 'oldest'} first |
| 313 | + </button> |
| 314 | +</div> |
| 315 | +``` |
| 316 | + |
| 317 | +Test out the button to make sure it works correctly. |
| 318 | + |
| 319 | +### Challenges |
| 320 | + |
| 321 | +#### Style the Todo Items |
| 322 | + |
| 323 | +If they are complete, style them to indicate that, perhaps by graying them out or using strikethrough. |
| 324 | + |
| 325 | +#### Add Input Form |
| 326 | + |
| 327 | +To create new todos, add an input form that will create a new todo item and add it to the state so it shows up in the list. |
| 328 | + |
| 329 | +#### Add `reactstrap` npm package to your CRA project |
| 330 | + |
| 331 | +If you haven't yet reviewed it, review the lesson on [React Styling](./styling-react.md). |
| 332 | + |
| 333 | +Let's use some prebuilt and prestyled components to make our Todo App look snazzier 😎 |
| 334 | + |
| 335 | +Since it has such good docs, there is an official page to do this: |
| 336 | + |
| 337 | +https://create-react-app.dev/docs/adding-bootstrap/ |
| 338 | + |
| 339 | +Try it out and see what you can do! |
| 340 | + |
| 341 | +### Deploy your Todo App using GitHub Pages Hosting |
| 342 | + |
| 343 | +https://github.com/gitname/react-gh-pages |
| 344 | + |
| 345 | +### Supplemental Materials |
| 346 | + |
| 347 | +- Official `create-react-app` docs: https://create-react-app.dev/ |
0 commit comments