mirror of
https://github.com/microsoft/frontend-bootcamp.git
synced 2026-01-26 14:56:42 +08:00
Day 1 updates
This commit is contained in:
@@ -8,7 +8,7 @@ Every website, application, form or component starts with markup. The HTML will
|
||||
|
||||
## Demo
|
||||
|
||||
In this exercise we will scaffold out some HTML for our Todo app, then add some basic styling to it.
|
||||
In this exercise we will scaffold out some HTML for our todo app, then add some basic styling to it.
|
||||
|
||||
### Page scaffold
|
||||
|
||||
@@ -20,14 +20,14 @@ In this exercise we will scaffold out some HTML for our Todo app, then add some
|
||||
</html>
|
||||
```
|
||||
|
||||
1. The DOCTYPE tells the browser that this file is written in modern HTML.
|
||||
2. The HTML tag wraps the entire page, and is the page root. Nothing is placed outside of those tags. Attributes can be set on HTML.
|
||||
3. Head will contain all of the page's meta data, in this case a link to our CSS file.
|
||||
4. Body is where all of the visible content should be placed.
|
||||
1. The [`DOCTYPE`](https://developer.mozilla.org/en-US/docs/Glossary/Doctype) tells the browser that this file is written in modern HTML.
|
||||
2. The `html` tag wraps the entire page and is the page root. Nothing is placed outside of this tag.
|
||||
3. `head` will contain all of the page's metadata, in this case a link to our CSS file.
|
||||
4. `body` is where all of the visible content should be placed.
|
||||
|
||||
### Content Sectioning
|
||||
|
||||
As we saw in the previous demo, HTML elements can be used to describe different content sections of the applications. Let's add `header`, `main` and `footer`, as well as populate the header with an `h1`, addTodo div, and `nav` for our filters.
|
||||
As we saw in the previous demo, HTML elements can be used to describe different content sections of the applications. Let's add `header`, `main` and `footer`, as well as populate the header with an `h1`, addTodo `div`, and `nav` for our filters.
|
||||
|
||||
```html
|
||||
<body>
|
||||
@@ -41,11 +41,11 @@ As we saw in the previous demo, HTML elements can be used to describe different
|
||||
</body>
|
||||
```
|
||||
|
||||
> Note that a `form` element would have been more semantic than a `div`, but we aren't using this form to POST to a server, so for this example a div is easier to use.
|
||||
> Note that a `form` element would have been more semantically correct than a `div`, but we aren't using this form to POST to a server, so for this example a div is easier to use.
|
||||
|
||||
### Updating the header
|
||||
|
||||
The header of our page is where most of the action is going to happen. First, lets give our app a name, adding 'TODO' to our `h1`. Then we can add an input and button to our `addTodo` div.
|
||||
The header of our page is where most of the action will happen. First, let's give our app a name, adding "TODO" to our `h1`. Then we can add an input and button to our `addTodo` div.
|
||||
|
||||
```html
|
||||
<input class="textfield" placeholder="add todo" /> <button class="submit">Add</button>
|
||||
@@ -53,7 +53,7 @@ The header of our page is where most of the action is going to happen. First, le
|
||||
|
||||
#### Navigation
|
||||
|
||||
The navigation for this application is quite simple. We want users to be able to switch between three filtered states. Since we need to track which state is currently selected, we'll add that as a class on the first item.
|
||||
The navigation for this application is quite simple. We want users to be able to switch between three filtered states. Since we need to track which state is currently selected, we'll add a `selected` class to the first item.
|
||||
|
||||
```html
|
||||
<nav class="filter">
|
||||
@@ -65,7 +65,7 @@ The navigation for this application is quite simple. We want users to be able to
|
||||
|
||||
### Adding styles
|
||||
|
||||
Now that we've got the top of our application scaffolded, we can add some styles in the head.
|
||||
Now that we've got the top of our application scaffolded, we can add some styles in the `head`.
|
||||
|
||||
```html
|
||||
<head>
|
||||
@@ -77,7 +77,7 @@ Now that we've got the top of our application scaffolded, we can add some styles
|
||||
|
||||
It looks like the selected button isn't getting any special styles. Let's dig in and see why that is.
|
||||
|
||||
Open up the browser inspector and target our 'all' button. You'll notice that the blue style is present on the list, but it is being overridden by the `border: none` above it. This is a situation where specificity is winning out over the cascade.
|
||||
Open up the browser inspector and target our "all" button. You'll notice that the blue style is present on the list, but it is being overridden by the `border: none` above it. This is a situation where specificity is winning out over the cascade.
|
||||
|
||||
> **Cascade** states that if two selectors are equal, the lowest one on the page wins
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# JavaScript Demo
|
||||
|
||||
Now that we a UI that looks like a todo app, we need to add functionality to make it **function** like a todo app. In this example we are going to use raw JavaScript to explicitly modify our application as we interact with it. This will be in stark contrast to the implicit approach we will take when we do this with React in the next exercise.
|
||||
Now that we have a UI that looks like a todo app, we need to make it **function** like a todo app. In this example we are going to use raw JavaScript to explicitly modify our application as we interact with it. This will be in stark contrast to the implicit approach we will take when we do this with React in the next exercise.
|
||||
|
||||
> Keep an eye on how often user actions directly modify the HTML on the page. You'll see this number drop to zero when we start using React.
|
||||
|
||||
@@ -8,29 +8,29 @@ Now that we a UI that looks like a todo app, we need to add functionality to mak
|
||||
|
||||
This demo starts off with a few elements already in place. Let's walk through what's already here.
|
||||
|
||||
- **clearInput()** - This is a generic, reusable function that takes in a `selector` parameter, finds the first matching element, and sets the element's value to an empty string. This direct modification is called a side effect.
|
||||
- **getTodoText()** - This is a quick helper function that returns the value inside of our textfield. Notice how some functions return values and how you can set that return to a variable.
|
||||
- **filter()** - This function takes in a `filterName` string, and a `button` which is a reference to the clicked button.
|
||||
1. Remove any `selected` class names.
|
||||
- `clearInput()` - This is a generic, reusable function that takes in a `selector` parameter, finds the first matching element, and sets the element's value to an empty string. This direct modification is called a **side effect**.
|
||||
- `getTodoText()` - This is a helper function that returns the value inside of our text field. Notice how some functions return values and how you can save that return value in a variable.
|
||||
- `filter()` - This function takes in a `filterName` string, and a `button` which is a reference to the clicked button.
|
||||
1. Remove the `selected` class from the previously selected element.
|
||||
2. Add `selected` to the clicked button.
|
||||
3. Set `filterName` to the clicked button's `innerText` value.
|
||||
4. Get all of the todos with [querySelectAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll), and then loop through them.
|
||||
4. Get all of the todos with [`querySelectAll`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll), and then loop through them.
|
||||
5. Set the `hidden` property of each todo based on the filter/state combination.
|
||||
|
||||
### Writing addTodo Function
|
||||
### Writing `addTodo` function
|
||||
|
||||
1. `todo` is set to equal the first todo item.
|
||||
2. `newTodo` is a clone of todo. Passing true means it is a deep clone, so we get the todo's children as well. Cloning does not duplicate the DOM node. We'll need to insert it in step 4.
|
||||
1. `todo` is set to the first todo item
|
||||
2. `newTodo` is a clone of `todo`. Passing true means it is a deep clone, so we get the todo's children as well. Cloning does not duplicate the DOM node. We'll need to insert it in step 4.
|
||||
> Note that this approach is very fragile, as it requires a todo node to always be present on the page.
|
||||
3. We set the innerText of the `<span class='title'>` to the value returned from getTodoText.
|
||||
> Note that if we left off the `()` we'd actually be assigning innerText to the 'function' instead of the function return.
|
||||
4. Insert our new todo into the todo's parent (the `ul`), before our reference todo. [insertBefore](https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore)
|
||||
3. We set the `innerText` of the `<span class='title'>` to the value returned from `getTodoText`
|
||||
> Note that if we left off the `()` we'd actually be assigning the *function* to `innerText` instead of the function's returned value.
|
||||
4. Insert our new todo into the todo's parent (the `ul`) before our reference todo using [`insertBefore`](https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore).
|
||||
|
||||
### Triggering functions from click events
|
||||
|
||||
Now that we have a working `addTodo` function, we need a way to trigger it when the user is ready. This can be done in two ways.
|
||||
|
||||
1. We can find the element with querySelector, then set its `onclick` to our function
|
||||
1. We can find the element with `querySelector`, then set its `onclick` to our function
|
||||
|
||||
```js
|
||||
document.querySelector('.addTodo .submit').onclick = addTodo;
|
||||
|
||||
@@ -52,12 +52,12 @@
|
||||
|
||||
const filterName = button.innerText;
|
||||
for (let todo of document.querySelectorAll('.todo')) {
|
||||
const checked = todo.querySelector('input').checked == true;
|
||||
if (filterName == 'all') {
|
||||
const checked = todo.querySelector('input').checked === true;
|
||||
if (filterName === 'all') {
|
||||
todo.hidden = false;
|
||||
} else if (filterName == 'active') {
|
||||
} else if (filterName === 'active') {
|
||||
todo.hidden = checked;
|
||||
} else if (filterName == 'completed') {
|
||||
} else if (filterName === 'completed') {
|
||||
todo.hidden = !checked;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,23 +2,23 @@
|
||||
|
||||
### Update Navigation
|
||||
|
||||
1. Add an onclick attribute to all 3 buttons in the navigation.
|
||||
2. For each onclick call the `filter` function. In our function we need a reference to the clicked button, so pass in the keyword `this` as the only parameter.
|
||||
1. Add an `onclick` attribute to all three buttons in the navigation.
|
||||
2. In each `onclick` call the `filter` function. In our function we need a reference to the clicked button, so pass in the keyword `this` as the only parameter.
|
||||
|
||||
### Write updateRemaining function
|
||||
### Write an `updateRemaining` function
|
||||
|
||||
1. Get a reference to the span with the `remaining` class, and store it in a variable.
|
||||
2. Use [querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) to get all of the todos.
|
||||
2. Use [`querySelectorAll`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) to get all of the todos.
|
||||
3. Set the `innerText` of the remaining span to the length of those todos.
|
||||
4. Add updateRemaining() to end of addTodo function.
|
||||
4. Add `updateRemaining()` to the end of the `addTodo` function.
|
||||
|
||||
### Write a clearCompleted function
|
||||
### Write a `clearCompleted` function
|
||||
|
||||
1. Get a reference to all of the todos with [querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll).
|
||||
1. Get a reference to all of the todos with [`querySelectorAll`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll).
|
||||
2. Use a `for (let todo of todos)` loop to iterate over each todo.
|
||||
3. Inside the for loop write an `if` statement to test if the `input` inside of the todo has a checked value of true.
|
||||
> Hint: you can use querySelector on any HTML node to find child elements within.
|
||||
4. Call `todo.remove()` for any todo whos input check is true.
|
||||
> Hint: you can use `querySelector` on any HTML element to find matching child elements.
|
||||
4. Call `todo.remove()` for any todo whose input is checked.
|
||||
5. After the loop is done, run `updateRemaining()`.
|
||||
6. Attach this function to the footer button.
|
||||
7. Test it out!
|
||||
|
||||
@@ -70,12 +70,12 @@
|
||||
|
||||
const filterName = button.innerText;
|
||||
for (let todo of document.querySelectorAll('.todo')) {
|
||||
const checked = todo.querySelector('input').checked == true;
|
||||
if (filterName == 'all') {
|
||||
const checked = todo.querySelector('input').checked === true;
|
||||
if (filterName === 'all') {
|
||||
todo.hidden = false;
|
||||
} else if (filterName == 'active') {
|
||||
} else if (filterName === 'active') {
|
||||
todo.hidden = checked;
|
||||
} else if (filterName == 'completed') {
|
||||
} else if (filterName === 'completed') {
|
||||
todo.hidden = !checked;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
function clearCompleted() {
|
||||
const todos = document.querySelectorAll('.todo');
|
||||
for (let todo of todos) {
|
||||
if (todo.querySelector('input').checked == true) {
|
||||
if (todo.querySelector('input').checked === true) {
|
||||
todo.remove();
|
||||
}
|
||||
}
|
||||
@@ -78,12 +78,12 @@
|
||||
|
||||
const filterName = button.innerText;
|
||||
for (let todo of document.querySelectorAll('.todo')) {
|
||||
const checked = todo.querySelector('input').checked == true;
|
||||
if (filterName == 'all') {
|
||||
const checked = todo.querySelector('input').checked === true;
|
||||
if (filterName === 'all') {
|
||||
todo.hidden = false;
|
||||
} else if (filterName == 'active') {
|
||||
} else if (filterName === 'active') {
|
||||
todo.hidden = checked;
|
||||
} else if (filterName == 'completed') {
|
||||
} else if (filterName === 'completed') {
|
||||
todo.hidden = !checked;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,23 +26,23 @@ export class App extends React.Component {
|
||||
}
|
||||
```
|
||||
|
||||
- **import React from 'react';** - This is how we [import modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) in JavaScript. This line creates a variable in this file called `React` that is equal to the default `export` of the `react` npm module.
|
||||
- **export class App** - Just like react exports code, our App component is nothing more than an exported "App" class. This allows us to import the class into other files.
|
||||
- **extends React.Component** - A JavaScript class is similar to other programming languages (it's a collection of methods and properties). Classes can also be extended, so when we create a React component class, we always extend the base React.Component class. Note that this `Component` class is coming from the `React` variable imported up top.
|
||||
> Note that `<any, any>` is necessary for TypeScript which we will touch on later.
|
||||
- **render()** - One of the methods defined by React.Component is the `render()` method. This is a function that defines the HTML the component is going to render.
|
||||
- **return** - Remember that functions can return values in addition to side effects, and this component is no different.
|
||||
- **Inside of the return?** It's HTML! Actually, it's JSX, but with very few exceptions you can treat it like HTML. A few key differences:
|
||||
1. Since 'class' is a reserved word in JavaScript, you will need to use className on your HTML tags `<div className="foo">`
|
||||
2. We can use custom HTML tags created by these render functions `<div><MyControl>hi</MyControl></div>`
|
||||
3. Controls can be self closing `<div><MyControl text='hi' /></div>`
|
||||
4. You can use JavaScript inside of JSX! If you declare `const name = 'Micah';` inside the render function, you can use that variable inside of your JSX `<div>{name}</div>` or `<div><MyControl text={name} /></div>`. Works with functions, loops, conditionals as well.
|
||||
- `import React from 'react';` - This is how we [import modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) in JavaScript. This line creates a variable in this file called `React` that is equal to the default `export` of the `react` npm module.
|
||||
- `export class App` - Just like React exports code, our App component is nothing more than an exported `App` class. This allows us to import the class into other files.
|
||||
- `extends React.Component` - A JavaScript class is similar to a class in other programming languages (it's a collection of methods and properties). Classes can also be extended, so when we create a React component class, we always extend the base `React.Component` class. (Note that this `Component` class is coming from the `React` variable imported up top.)
|
||||
- `render()` - One of the methods defined by `React.Component` is the `render()` method, which defines the HTML the component is going to render.
|
||||
- `return` - Remember that functions can return values in addition to having side effects, and this component is no different.
|
||||
|
||||
**Inside of the return?** It's HTML! Actually, it's [JSX](https://reactjs.org/docs/introducing-jsx.html), but with very few exceptions you can treat it like HTML. A few key differences:
|
||||
- Since `class` is a [reserved word](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords) in JavaScript, you will need to use `className` on your HTML tags: `<div className="foo">`
|
||||
- We can use custom HTML tags corresponding to the React components we create: `<div><MyControl>hi</MyControl></div>`
|
||||
- Controls can be self-closing: `<div><MyControl text='hi' /></div>`
|
||||
- You can use JavaScript inside of JSX! If you declare `const name = 'Micah';` inside the render function, you can use that variable inside of your JSX like `<div>{name}</div>` or `<div><MyControl text={name} /></div>`. This works with function calls and some types of conditionals as well.
|
||||
|
||||
### index.tsx
|
||||
|
||||
This is the file that places your App onto the page.
|
||||
|
||||
> Note that to avoid build errors, this file has been renamed to index.temp. Change the name to index.tsx.
|
||||
> Note that to avoid build errors, this file has been renamed to `index.temp`. Change the name to `index.tsx`.
|
||||
|
||||
```ts
|
||||
import React from 'react';
|
||||
@@ -51,21 +51,21 @@ import { App } from './App';
|
||||
ReactDOM.render(<App />, document.getElementById('app'));
|
||||
```
|
||||
|
||||
- **import ReactDOM from "react-dom";** - We've seen React imported before, but now we're also grabbing ReactDom from a package called "react-dom".
|
||||
> Note that this casing is intentional. NPM packages are kabab-case, exported items are usually camelCase or PascalCase. PascalCase is usually used for 'proper noun' exports. ProjectName, ComponentName etc.
|
||||
- **import { App } from "./App";** - If we had exported our app like this: `export default class extends React.Component`, this line would look like the lines above - `import App from "./App";`. But React convention is to use named exports, which can easily be extracted like this `{ App }`.
|
||||
- `import ReactDOM from "react-dom";` - We've seen React imported before, but now we're also grabbing `ReactDOM` from a package called `react-dom`.
|
||||
> Note that this casing is intentional. Usually, NPM packages are kebab-case and exported items are camelCase or PascalCase. PascalCase is usually used for "proper noun" exports: ProjectName, ComponentName, etc.
|
||||
- `import { App } from './App';` - If we had exported our app using `export default class App extends React.Component`, this line would look similar to the lines above - `import App from './App';`. But the convention for React components is to use named exports, which can easily be extracted using syntax like `{ App }`.
|
||||
> This notation is called [object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring), and it's awesome!
|
||||
- **ReactDOM.render...** This line calls the render function inside of ReactDOM and attaches our `<App />` component to the element with `id=app`. Take a peak in the index.html file. Shouldn't be too hard to find it.
|
||||
- `ReactDOM.render...` - This line calls the render function inside of `ReactDOM` and attaches our `<App />` component to the element with `id=app`. Take a peek in the index.html file. Shouldn't be too hard to find it.
|
||||
|
||||
### Counter Component
|
||||
|
||||
In this example we'll start with an already scaffolded out control. The goal of our counter is to keep track of how many times the counter button is clicked. In the past JavaScript demo we might grab a reference to `document.querySelector('.counter')` and then manually increment the number we find there. While using the DOM as you data store works, it's REALLY hard to scale past the most basic demo.
|
||||
In this example we'll start with an already scaffolded out control. The goal of our counter is to track how many times the counter button is clicked. In the past JavaScript demo we might have accessed the counter element using `document.querySelector('.counter')` and manually incremented the number found there. While using the DOM as your data store works, it's REALLY hard to scale past the most basic demo.
|
||||
|
||||
React solves this by allowing each control to specify its own data store, called 'state'. We can reference values in state when we render our UI, and we can also update state over the lifetime of our application.
|
||||
React solves this by allowing each control to specify its own data store, called **state**. We can reference values in state when we render our UI, and we can also update state over the lifetime of our application.
|
||||
|
||||
#### Adding State
|
||||
|
||||
JavaScript uses a `constructor` method to instantiate each copy of a class. So for class based controls, this is where we specify state.
|
||||
JavaScript uses a `constructor` method to instantiate each copy of a class. So for class-based controls, this is where we define an initial value for `state`.
|
||||
|
||||
```js
|
||||
constructor(props) {
|
||||
@@ -76,13 +76,13 @@ constructor(props) {
|
||||
}
|
||||
```
|
||||
|
||||
- The constructor takes in the component props (values passed into the control).
|
||||
- the [super()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super) function is called to gain access to some functionality in React.Component
|
||||
- Now we can define any state variables we want to use in the control, and give them a default value. This counter value can now be accessed via `this.state.counter`. We can also update state by calling `this.setState({counter: 1})`
|
||||
- The constructor takes in the component's props (values passed into the control).
|
||||
- The [`super()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super) function calls the constructor of the parent class (in this case `React.Component`) to do any shared setup.
|
||||
- Now we can define any state variables we want to use in the control and give them a default value. Our counter value can now be accessed via `this.state.counter`. Later, we can update state by calling `this.setState({ counter: 1 })`.
|
||||
|
||||
#### Using [object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring) for props and state
|
||||
|
||||
Both props are state are JavaScript objects. They have a bunch of key value pairs in them which you can access via `this.props.foo` or `this.state.bar`. Sometimes they have **MANY** values inside of them which you need access to. You could do this:
|
||||
Both `props` are `state` are JavaScript objects. They have a bunch of key/value pairs in them which you can access via `this.props.foo` or `this.state.bar`. Sometimes they have MANY values inside of them which you need access to. You could do this:
|
||||
|
||||
```js
|
||||
let cat = this.props.cat;
|
||||
@@ -92,15 +92,15 @@ let pig = this.props.pig;
|
||||
let cow = this.props.cow;
|
||||
```
|
||||
|
||||
> Note that we access props and state on `this`, which is how you reference all of the class properties and methods.
|
||||
> Note that we access `props` and `state` on `this`, which is how you reference all class properties and methods.
|
||||
|
||||
But this is verbose and repetitive. Instead you can use destructuring to turn this into a one liner.
|
||||
But this is verbose and repetitive. Instead you can use destructuring to turn this into a one-liner.
|
||||
|
||||
```js
|
||||
let { cat, dog, bird, pig, cow } = this.props;
|
||||
```
|
||||
|
||||
So even though this isn't 100% necessary today, it does future proof our code if we add more props or state later. So let's add this inside of the render method, but above the return:
|
||||
Even though this isn't 100% necessary today, it does future-proof our code if we add more values to `props` or `state` later. So let's add this inside of the `render` method, above the `return`:
|
||||
|
||||
```js
|
||||
const { counter } = this.state;
|
||||
@@ -118,9 +118,11 @@ return (
|
||||
);
|
||||
```
|
||||
|
||||
Each JSX return needs to be a single element, so start with a wrapping `<div>`. Inside of that we can add the `text` we get from `this.props`, then after a colon, the `counter` we pulled in from `this.state`. This will render as the string `My Text Prop: 0`. After that let's add a button we'll use later. For now, we're going to see how we can use this in our app.
|
||||
Each JSX return value needs to be a single element, so start with a wrapping `<div>`. Inside of that we can add the `text` we get from `this.props`, then after a colon, the `counter` we pulled in from `this.state`. This will render as the string `My Text Prop: 0`. After that let's add a button we'll use later.
|
||||
|
||||
#### Updating the App to use Counters
|
||||
Now let's see how we can use this component in our app.
|
||||
|
||||
#### Updating the App to Use Counters
|
||||
|
||||
Before we can use our `Counter`, we need to import it into the App file.
|
||||
|
||||
@@ -128,7 +130,7 @@ Before we can use our `Counter`, we need to import it into the App file.
|
||||
import { Counter } from './components/Counter';
|
||||
```
|
||||
|
||||
Now that we have access to `Counter`, we can add it to the App just as if it were an HTML element.
|
||||
Now that we have access to `Counter`, we can use it in the App just as if it were an HTML element.
|
||||
|
||||
```jsx
|
||||
return (
|
||||
@@ -140,11 +142,11 @@ return (
|
||||
);
|
||||
```
|
||||
|
||||
> Note the capitalization of Counter. HTML might not be case sensitive, but JSX is! A common practice is to use the capitalized versions of HTML elements to name their JSX counterpart. Button, Select, Label, Form etc.
|
||||
> Note the capitalization of `Counter`. HTML might not be case-sensitive, but JSX is! A common practice is to use the capitalized names of HTML elements to name corresponding React components: Button, Select, Label, Form, etc.
|
||||
|
||||
### Exploring Component Props
|
||||
|
||||
Now that we've got two Counters on our page, we can see that the string passed into the `text` attribute got passed into our Counter, and rendered on the page. Being able to pass values into controls make them more flexible and reusable. Props can be strings, numbers, booleans, and even arrays and objects.
|
||||
Now that we've got two counters on our page, we can see that the string passed into the `text` attribute got passed into our counter and rendered on the page. Being able to pass values (props) into controls makes them more flexible and reusable. Props can be strings, numbers, booleans, and even arrays and objects.
|
||||
|
||||
```jsx
|
||||
<MyComponent
|
||||
@@ -160,13 +162,15 @@ Now that we've got two Counters on our page, we can see that the string passed i
|
||||
/>
|
||||
```
|
||||
|
||||
> Note that all non string values are passed through as JavaScript by wrapping it in `{}`
|
||||
> Note that all non-string values are passed through as JavaScript by wrapping them in `{}`.
|
||||
|
||||
### Writing our Button Click
|
||||
### Writing our Button Click Handler
|
||||
|
||||
Our next step is to wire up the button to increment the `counter` in our component state. This will very similar to what we did in step 3, but instead of placing the function in a script tag, we can create it as a class method, and keep it out of the global scope.
|
||||
|
||||
> By convention we place methods below render, and private methods (those for internal use only) are prefixed with an underscore.
|
||||
> By convention we place other methods below render, and private methods (those for internal use only) are prefixed with an underscore.
|
||||
|
||||
This function will update our component's state, incrementing the counter value by 1. (Note that `setState` only modifies the values of keys listed in object passed as its parameter.)
|
||||
|
||||
```jsx
|
||||
_onButtonClick = () => {
|
||||
@@ -174,25 +178,21 @@ _onButtonClick = () => {
|
||||
};
|
||||
```
|
||||
|
||||
This function will update our component state, incrementing our counter value by 1. Note that setState only affects the values of keys we have listed.
|
||||
> This isn't exactly a method, but a class property that is set to an [arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). This mostly works the same as `onButtonClick() { }` but eliminates the need for [extra boilerplate](https://medium.freecodecamp.org/this-is-why-we-need-to-bind-event-handlers-in-class-components-in-react-f7ea1a6f93eb) used to avoid potential "gotchas" with [how `this` works in JavaScript](https://codeburst.io/javascript-the-keyword-this-for-beginners-fb5238d99f85).)
|
||||
|
||||
> Note that this isn't exactly a method, but a property that is equal to a [arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). This works just as well as `onButtonClick() { }`, but doesn't require extra binding up in the constructor.
|
||||
|
||||
> Also note that `setState()` may update the state [asynchronously](https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous). If the next state depends on the previous state or props, then we should use the second form of `setState()` which takes a function that receives the previous state as the first argument and the props as the second.
|
||||
|
||||
Now that we have a function to increment out count, all that we have left is to connect it to our button.
|
||||
Now that we have a function to increment our count, all that's left is to connect it to our button.
|
||||
|
||||
```jsx
|
||||
<button onClick={this._onButtonClick}>Click</button>
|
||||
```
|
||||
|
||||
> Note the syntax is a bit different than HTML. `onclick="funcName()"` vs `onClick={this.funcName}`
|
||||
> Note the syntax is a bit different than in HTML: `onclick="funcName()"` in HTML vs `onClick={this.funcName}` in JSX.
|
||||
|
||||
> Also note that each Counter maintains its own state! You can modify the state inside of one without affecting the others.
|
||||
> Also note that each Counter maintains its own state! You can modify the state inside of one counter without affecting 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 `<button></button>` with `<Button></Button>`. Let's take a quick look at Button to see how it came together.
|
||||
Buttons are among the most commonly written components. Custom buttons help abstract common styling, add icons or other decorations, and increase functionality (menu buttons etc). Let's take a quick look at a custom button component to see how it comes together.
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
@@ -207,9 +207,9 @@ export const Button = props => {
|
||||
};
|
||||
```
|
||||
|
||||
- All controls need to import React (don't worry, only 1 copy ever gets into your app).
|
||||
- Importing CSS files into the component means that the CSS is only loaded if the component is used.
|
||||
- React components can be created as a class **or** as a function. In this function, props are passed in as a function parameter.
|
||||
> Until recently, you could only access state in class based components. But with the advent of [hooks](https://reactjs.org/docs/hooks-intro.html) you can create stateful function components.
|
||||
- Since this is a function, we don't have any methods, including `render()`. Just return your JSX as you would in a class based component.
|
||||
- `props.children` is anything passed between the opening and closing tags `<Button>I'm children</Button>`
|
||||
- All components need to import React (don't worry, only one copy ever gets into your app)
|
||||
- CSS files imported into the component are only loaded if the component is used
|
||||
- React components can be created as a class **or** as a function. In this function component, props are passed in as a function parameter.
|
||||
> Until recently, you could only access state in class-based components. But with the advent of [hooks](https://reactjs.org/docs/hooks-intro.html) you can create stateful function components.
|
||||
- Since this is a function, we don't have any methods, including `render()`. Just return your JSX as you would in the render function of a class-based component.
|
||||
- `props.children` contains anything passed between the opening and closing tags: `<Button>I'm in children</Button>`
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
## Demo
|
||||
|
||||
To start off our Todo application we are going to follow the steps outline in [Thinking in React](https://reactjs.org/docs/thinking-in-react.html). The first step of the process is to break our application into a component hierarchy. For this app, we're going to keep it simple and just use four parts.
|
||||
To start off our todo application we are going to follow the steps outlined in [Thinking in React](https://reactjs.org/docs/thinking-in-react.html). The first step of the process is to break our application into a component hierarchy. For this app, we're going to keep it simple and just use four parts.
|
||||
|
||||
- TodoHeader
|
||||
- TodoList
|
||||
-TodoListItem
|
||||
- TodoListItem
|
||||
- TodoFooter
|
||||
|
||||
We could go a lot deeping creating buttons, inputs and checkboxes, but this is a great place start. Often you'll want to start with a single large control, and then start breaking it up into smaller pieces.
|
||||
We could go a lot deeper into creating buttons, inputs and checkboxes, but this is a great place to start. Often you'll want to start with a single large control and then break it up into smaller pieces.
|
||||
|
||||
### TodoApp
|
||||
|
||||
@@ -32,13 +32,13 @@ export class TodoApp extends React.Component {
|
||||
}
|
||||
```
|
||||
|
||||
We'll start off with all of the file scaffolded and imported into our App. This will let us dive right into each control and see updates quickly.
|
||||
We'll start off with all of the files scaffolded and imported into our App. This will let us dive right into each control and see updates quickly.
|
||||
|
||||
### TodoHeader
|
||||
|
||||
Our objective is to create a static version of our application, so we'll copy over the entire header tag, minus any function calls we may have added.
|
||||
Our objective for now is to create a static version of our application, so we'll copy over the entire header tag from a previous step, minus any function calls we added.
|
||||
|
||||
> Note that since this is React we had to change `class` to `className`, otherwise nothing changes
|
||||
> Note that since this is React we had to change `class` to `className`, but nothing else changes.
|
||||
|
||||
```jsx
|
||||
return (
|
||||
@@ -59,7 +59,7 @@ return (
|
||||
|
||||
### TodoListItem
|
||||
|
||||
Anytime you see repeated complex elements, that is usually a sign to create a new component. With a few props you can typically abstract all of those elements into a single component. This is certainly the case with Todos items.
|
||||
Any time you see repeated complex elements, that is usually a sign that you should create a new component. With a few props you can typically abstract all of those elements into a single component. This is certainly the case with todo items.
|
||||
|
||||
```jsx
|
||||
return (
|
||||
@@ -71,4 +71,4 @@ return (
|
||||
);
|
||||
```
|
||||
|
||||
> Note that I've removed the title span as it was only needed to make targeting that text easier
|
||||
> Note that I've removed the title span as it was only needed to make targeting that text easier.
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# Creating a State Driven UI
|
||||
# Creating a State-Driven UI
|
||||
|
||||
In React data travels two directions, top down in the form of state propagating 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.
|
||||
In React, data travels two directions: top-down in the form of state propagating 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
|
||||
## Demo
|
||||
|
||||
[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 [reconciliation](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.
|
||||
[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 [reconciliation](https://reactjs.org/docs/reconciliation.html) process, figuring out what in your UI needs to change based on changing state, is what React excels at.
|
||||
|
||||
### Adding State to App
|
||||
|
||||
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.
|
||||
|
||||
So here is our full constructor
|
||||
So here is our full constructor:
|
||||
|
||||
```jsx
|
||||
constructor(props) {
|
||||
@@ -39,11 +39,11 @@ constructor(props) {
|
||||
}
|
||||
```
|
||||
|
||||
> 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.
|
||||
> You could also 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.
|
||||
|
||||
### 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.
|
||||
To avoid reaching into state over and over, we once again use destructuring to pull out the pieces we need.
|
||||
|
||||
```jsx
|
||||
const { filter, todos } = this.state;
|
||||
@@ -61,9 +61,9 @@ return (
|
||||
);
|
||||
```
|
||||
|
||||
### State Driven TodoList
|
||||
### 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.
|
||||
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 our todo items.
|
||||
|
||||
```jsx
|
||||
{
|
||||
@@ -71,43 +71,45 @@ I've already pulled out our props into `filter` and `todos` variables, and writt
|
||||
}
|
||||
```
|
||||
|
||||
- **map**: A JavaScript map takes in an array (filteredTodos) and transforms it into a new array (our rendered TodoListItems)
|
||||
- **key**: 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.
|
||||
- **id**: 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.
|
||||
- **todos[id]**: 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.
|
||||
- [`map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map): This method transforms the array it's called on into a new array (our rendered TodoListItems).
|
||||
- `key`: We use the `id` from the `filterTodos` array as the [list item key](https://reactjs.org/docs/lists-and-keys.html) to help React track each item as state changes and the component re-renders.
|
||||
- `id`: The `key` is not actually passed into the component, so we pass the same value as `id` as well. This will help us out later.
|
||||
- `todos[id]`: 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 operator 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
|
||||
### 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. Fortunately, this is no problem at all for React. First off let's deal with the incoming state.
|
||||
|
||||
#### Conditional ClassNames
|
||||
#### Conditional Class Names
|
||||
|
||||
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.
|
||||
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
|
||||
<nav className="filter">
|
||||
<button className={filter == 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'selected' : ''}>completed</button>
|
||||
<button className={filter === 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter === 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter === 'completed' ? 'selected' : ''}>completed</button>
|
||||
</nav>
|
||||
```
|
||||
|
||||
> Ternary operators are very popular in React code as each expression could be a string for a className, or even a JSX element.
|
||||
> The [ternary operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator) `condition ? expressionIfTrue : expressionIfFalse` is widely used in React code, as each expression could be a string for a className or even a JSX element.
|
||||
|
||||
#### Creating a Controlled Input
|
||||
#### Adding a Controlled 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.
|
||||
In traditional 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 whose 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
|
||||
1. A state variable to hold the input's value:
|
||||
|
||||
```jsx
|
||||
this.state = { labelInput: '' };
|
||||
```
|
||||
|
||||
2. A function to update that value
|
||||
2. A callback function to update that value:
|
||||
|
||||
```jsx
|
||||
_onChange = evt => {
|
||||
|
||||
@@ -17,9 +17,9 @@ export class TodoHeader extends React.Component<any, any> {
|
||||
<button className="submit">Add</button>
|
||||
</div>
|
||||
<nav className="filter">
|
||||
<button className={filter == 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'selected' : ''}>completed</button>
|
||||
<button className={filter === 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter === 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter === 'completed' ? 'selected' : ''}>completed</button>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -17,9 +17,9 @@ export class TodoHeader extends React.Component<any, any> {
|
||||
<button className="submit">Add</button>
|
||||
</div>
|
||||
<nav className="filter">
|
||||
<button className={filter == 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'selected' : ''}>completed</button>
|
||||
<button className={filter === 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter === 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter === 'completed' ? 'selected' : ''}>completed</button>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -17,9 +17,9 @@ export class TodoHeader extends React.Component<any, any> {
|
||||
<button className="submit">Add</button>
|
||||
</div>
|
||||
<nav className="filter">
|
||||
<button className={filter == 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'selected' : ''}>completed</button>
|
||||
<button className={filter === 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter === 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter === 'completed' ? 'selected' : ''}>completed</button>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -17,9 +17,9 @@ export class TodoHeader extends React.Component<any, any> {
|
||||
<button className="submit">Add</button>
|
||||
</div>
|
||||
<nav className="filter">
|
||||
<button className={filter == 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'selected' : ''}>completed</button>
|
||||
<button className={filter === 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter === 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter === 'completed' ? 'selected' : ''}>completed</button>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -29,13 +29,13 @@ export class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState
|
||||
</button>
|
||||
</div>
|
||||
<nav className="filter">
|
||||
<button onClick={this._onFilter} className={filter == 'all' ? 'selected' : ''}>
|
||||
<button onClick={this._onFilter} className={filter === 'all' ? 'selected' : ''}>
|
||||
all
|
||||
</button>
|
||||
<button onClick={this._onFilter} className={filter == 'active' ? 'selected' : ''}>
|
||||
<button onClick={this._onFilter} className={filter === 'active' ? 'selected' : ''}>
|
||||
active
|
||||
</button>
|
||||
<button onClick={this._onFilter} className={filter == 'completed' ? 'selected' : ''}>
|
||||
<button onClick={this._onFilter} className={filter === 'completed' ? 'selected' : ''}>
|
||||
completed
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
Reference in New Issue
Block a user