diff --git a/step1-04/README.md b/step1-04/README.md index eeb2ad7..3876cef 100644 --- a/step1-04/README.md +++ b/step1-04/README.md @@ -184,6 +184,8 @@ Now that we have a function to increment out count, all that we have left is to > Note the syntax is a bit different than HTML. `onclick="funcName()"` vs `onClick={this.funcName}` +> Also note that each Counter maintains its own state! You can modify the state insde of one without affect the others. + ## Bonus: Using a Button component Buttons are one of the most common components to write. They help abstract common styling, add icons or other decorations, and increase functionality (menu buttons etc). Using an existing Button component is as easy as importing it `import {Button} from './Button';` and replacing `` with ``. Lets take a quick look at button to see how it came together. diff --git a/step1-05/README.md b/step1-05/README.md index c05ea71..1006f06 100644 --- a/step1-05/README.md +++ b/step1-05/README.md @@ -1,4 +1,4 @@ -# Thinking In React: Hierarchy and Building a Static Version +# Building a Static Page ## Demo diff --git a/step1-06/README.md b/step1-06/README.md index 8825198..4d393b4 100644 --- a/step1-06/README.md +++ b/step1-06/README.md @@ -1,20 +1,135 @@ +# Creating a State Driven UI + +In React data travels two directions, top down in the form of state propegating throughout controls, and bottom up, as interacting with the UI flows back up to modify the state. When writing an application it's often helpful to think of these two directions as separate parts of the development process. + ## demo -add state to AppTodo -pass to header and list +[Step #3 of Thinking in React](https://reactjs.org/docs/thinking-in-react.html) suggests finding the "minimal set of mutable state" that your application requires. So in this demo we are going to add that "minimal state" to our application and drive our UI off of that data. With that done the next step will be to create ways to modify that state, which will in turn cascade down through our UI. This [reconcilation](https://reactjs.org/docs/reconciliation.html) process, figuring out what in your UI needs to change based on changing state, is what React excels in. -### header +### Adding State to App -- pull filter from props -- update buttons with classNames for active -- Add value/onChange to input +For our minimal state, we're going to include just two keys: `todos` and `filter`. We don't need to worry about a `remaining` value because we can calculate that by looking at the number of unchecked todos. -### TodoList +So here is our full constructor -- write map to loop through items, pass key and todos +```jsx +constructor(props) { + super(props); + this.state = { + todos: { + '04': { + label: 'Todo 4', + completed: true + }, + '03': { + label: 'Todo 3', + completed: false + }, + '02': { + label: 'Todo 2', + completed: false + }, + '01': { + label: 'Todo 1', + completed: false + } + }, + filter: 'all' + }; +} +``` -## exercise +> You could use an array to represent your todos. Array manipulation can be easier in some cases, but this object approach simplifies other functionality and will ultimately be more performant. -update footer to include todos -add item count and item(s) -update ListItem, pull in props, use label/completed already passed in +### Passing State Through to UI + +To avoid reaching into state over and over, we once again use deconstruction to pull out the pieces we need. + +```jsx +const { filter, todos } = this.state; +``` + +Now we can pass `filter` and `todos` into our components. + +```jsx +return ( +
+ + + +
+); +``` + +### State Driven TodoList + +I've already pulled out our props into `filter` and `todos` variables, and written a bit of JS that will return an array of filtered todo `id`s. We'll be using that filtered array to render out todo items. + +```jsx +{ + filteredTodos.map(id => ); +} +``` + +- A JavaScript map takes in an array and transforms it into a new array +- We use the `id` from the `filterTodos` array as the [list key](https://reactjs.org/docs/lists-and-keys.html) to help React track each item as state changes. +- The key is not actually passed into the component, so we pass the key in as `id` as well. This will help us out later. +- Lastly we use the `id` to grab the todo from our `todos` object, then use the [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) to pass through the todo's `label` and `completed` values. + > This spread opperator is the same as saying `label={todos[id].label} completed={todos[id].completed}`. Pretty obvious why spread is so handy! + +### State Driven and Stateful Header + +Within the header we've got a situation where we not only want to pass `filter` state down to it, but we also want to maintain state within the control. Fortunatly, this is no problem at all for React. First off let's deal with the incoming state. + +#### Conditional ClassNames + +In CSS based styling, visual states are applied by adding and removing classes. We can use the filter value to conditionally add a class thereby lighting up the correct filter button. + +```jsx + +``` + +> Terniary operators are very popular in React code as each expression could be a string for a className, or even a JSX element. + +#### Creating a Controled Input + +In tradition HTML forms users interact with the form, and on submit, those values are captured and transmitted. Those are called **uncontrolled inputs**. A **controlled input** is one whos value is defined by state, and interaction with that input updates state with each keystroke. This round trip process might sound inefficient, but in reality it has little to no impact, and it enables some advanced form functionality. + +To create a controlled component, we need two things, which our demo already provides: + +1. A state variable to hold the input's value + +```jsx +this.state = { labelInput: '' }; +``` + +2. A function to update that value + +```jsx +_onChange = evt => { + this.setState({ labelInput: evt.target.value }); +}; +``` + +With those two pieces in place, we can update our uncontrolled input to being controlled. + +```jsx + +``` + +## Exercise + +### TodoFooter + +1. Use the provided `itemCount` value drive the number of items left. +2. Use a ternary operator to print `item` vs `items` based on if `itemCount > 1` + +### TodoListItem + +1. Pull in `label` and `completed` from props using [destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring) +2. Set the todo's text to `label` and the `checked` prop to `completed` + > Note that this is only half the work we need to do to make these controlled inputs. What is the other half? diff --git a/step1-06/demo/src/components/TodoHeader.tsx b/step1-06/demo/src/components/TodoHeader.tsx index f310531..3209be8 100644 --- a/step1-06/demo/src/components/TodoHeader.tsx +++ b/step1-06/demo/src/components/TodoHeader.tsx @@ -7,6 +7,7 @@ export class TodoHeader extends React.Component { } render() { + const { filter } = this.props; return (

todos

diff --git a/step1-06/exercise/src/components/TodoList.tsx b/step1-06/exercise/src/components/TodoList.tsx index a92faa0..9f605f5 100644 --- a/step1-06/exercise/src/components/TodoList.tsx +++ b/step1-06/exercise/src/components/TodoList.tsx @@ -11,7 +11,7 @@ export class TodoList extends React.Component { return (
    {filteredTodos.map(id => ( - + ))}
); diff --git a/step1-06/final/src/components/TodoHeader.tsx b/step1-06/final/src/components/TodoHeader.tsx index dcf8947..17a8b3e 100644 --- a/step1-06/final/src/components/TodoHeader.tsx +++ b/step1-06/final/src/components/TodoHeader.tsx @@ -1,6 +1,11 @@ import React from 'react'; export class TodoHeader extends React.Component { + constructor(props) { + super(props); + this.state = { labelInput: '' }; + } + render() { const { filter } = this.props; diff --git a/step1-06/final/src/components/TodoList.tsx b/step1-06/final/src/components/TodoList.tsx index a92faa0..9f605f5 100644 --- a/step1-06/final/src/components/TodoList.tsx +++ b/step1-06/final/src/components/TodoList.tsx @@ -11,7 +11,7 @@ export class TodoList extends React.Component { return (
    {filteredTodos.map(id => ( - + ))}
); diff --git a/step1-07/README.md b/step1-07/README.md index 75c4945..e5c450d 100644 --- a/step1-07/README.md +++ b/step1-07/README.md @@ -1,10 +1,113 @@ -already done -Types in header -TodoApp methods +# Creating a UI Driven State -filteredTodos in List +Now that we have a UI that is purely driven by the state of our app, we need to add functionality to allow the UI to drive the state. This is often done by creating functions that call `setState` like we saw in the `todoHeader`, that are passed down to the UI as props. -demo +> We'll be learning in part 2 of this workshop how we can expose these functions without explicitly passing them down via props + +This is our core 'business logic' and handles everything our basic 'CRUD' operations of "Create, Read, Update, Delete". We don't have time to walk through writing all of those functions, but you can see that they are already provided in the demo's `TodoApp.tsx`. + +## Intro to Typescript + +Taking a look at our components in `TodoApp` you can see that our list of props is not just getting longer, but is getting much more complex! We're passing through functions with various signatures, complex `todos` objects as well as filter strings which are always one of three values. + +As applications grow, it becomes increasing difficult to remember what each function does, or what each todo contains. Also, as JavaScript is a loosly type language, if I wanted to change the value of `todos` to an array inside my `TodoList`, javascript wouldn't care. But if `TodoListItems` was expecting an object, our application would break. + +It is because of these two reasons that the entire industry is shifting to writing applications that are strongly typed, and are using Typescript to accomplish that. + +As [their website](https://www.typescriptlang.org/) state: + +> Typescript is a superset of JavaScript that compiles to plain JavaScript + +If you've ever used [Sass](https://sass-lang.com/) you are familiar with this concept. In the same say that all valid CSS is valid Sass, all valid JavaScript is valid Typescript. That's why most of this project has been writting in `ts` and `tsx` files instead of `js` and `jsx` files. + +Let's dive into the demo and see how Typescript can help us better understand our component props, and guard against future regressions. + +## Demo + +Let's start off in the TodoList, as that has the most data flow, up and down. + +Looking at our `TodoApp` we know that `TodoList` has three props, `filter`, `todos`, and `filter`. We'll start by creating and interface that represents this component's props called `TodoListProps`. + +```tsx +interface TodoListProps { + filter: any; + todos: any; + complete: any; +} +``` + +> Note that we're using the `any` keyword for now. This won't give us any type safety, but it does innumerate the valid props we can pass to this component. + +With that interface written, we'll add it to our component class. + +```tsx +export class TodoList extends React.Component +``` + +> Note that the first value in `<>` is for a props interface, and the second for state + +Now that we have a typed component, let's go back to our `TodoApp` and see what happens if we try to change the name of a prop. + +### Adding type safety + +So far we've only established what our prop names are, not the values inside of them. Let's first look at `filter`, and see how we can improve that prop's type safety. + +#### Filter Type + +We know that filter shouldn't be an object, array or function, so we can specify it should always be a string like this: + +```tsx +interface TodoListProps { + filter: string; + todos: any; + complete: any; +} +``` + +But since we know that the filter can be only one of three values, we can explicitly write it that way with [union types](https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types): + +```tsx +interface TodoListProps { + filter: 'all' | 'active' | 'completed'; + todos: any; + complete: any; +} +``` + +Now try going back to `TodoApp` and changing the `filter` attribute in `TodoList` to something else. + +#### Complete Type + +The `complete` props isn't data, but rather a function. Fortunatly, Typescript can handle function types just as well as data. + +```tsx +interface TodoListProps { + filter: 'all' | 'active' | 'completed'; + todos: any; + complete: (id: string) => void; +} +``` + +For functions we are only concerned with the parameters passed in and the return value. You can see in the example above that the function takes in an `id` of type string, and returns `void`, which means it has no return. + +### Todos Type + +The `todos` prop is interesting in that `todos` is an object with a bunch of unknown keys. So here's what that interface would look like. + +```tsx +interface TodoListProps { + filter: 'all' | 'active' | 'completed'; + todos: { + [id: string]: { + label: string; + completed: boolean; + }; + }; + complete: (id: string) => void; +} +``` + +> Note that the `[]` notation does not mean an array, it is a [computed property](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Computed_property_names) notation. ## app diff --git a/step1-07/final/src/TodoApp.tsx b/step1-07/final/src/TodoApp.tsx index b24da27..43ce7f9 100644 --- a/step1-07/final/src/TodoApp.tsx +++ b/step1-07/final/src/TodoApp.tsx @@ -26,8 +26,6 @@ export class TodoApp extends React.Component { const { todos } = this.state; const id = index++;