Коммит
9e05bc6be2
|
@ -30,7 +30,7 @@ export const TodoFooter = connect(
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
|
@ -63,7 +63,7 @@ function displayMatches() {
|
|||
|
||||
### Iteration
|
||||
|
||||
Next we'll update our function to iterate through a string of letters. We loop over each letter using the [`for of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) syntax. We'll use real input later, but for now this varifies that our function is working.
|
||||
Next we'll update our function to iterate through a string of letters. We loop over each letter using the [`for of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) syntax. We'll use real input later, but for now this verifies that our function is working.
|
||||
|
||||
```js
|
||||
function displayMatches() {
|
||||
|
@ -90,7 +90,7 @@ function displayMatches() {
|
|||
}
|
||||
```
|
||||
|
||||
> In JavaScript, it's safest to use strict `===` for comparisons, because `==` will try to convert the operands to the same type (and sometimes the behavior is [not what you'd expect](https://www.youtube.com/watch?v=et8xNAc2ic8)). For example, `"1" == 1` is true whereas `"1" === 1` would be false.
|
||||
> In JavaScript, it's safest to use strict `===` for comparisons, because `==` will try to convert the operands to the same type. For example, `"1" == 1` is true whereas `"1" === 1` is false, but the behavior in certain other cases is [not what you'd expect](https://www.youtube.com/watch?v=et8xNAc2ic8). (See [this video](https://www.destroyallsoftware.com/talks/wat) for more strange JavaScript behavior.)
|
||||
|
||||
### Interacting with the DOM
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
2. Add 4 list items with class `todo` inside of that list with the following content
|
||||
`<label><input type="checkbox" /> <span class="title"> Todo </span> </label>`
|
||||
3. Add a span and a button to your footer
|
||||
4. Span content should be `<i class="remaining>4</i> items left` and button should say `Clear Completed` and have a class of `submit`
|
||||
4. Span content should be `<i class="remaining">4</i> items left` and button should say `Clear Completed` and have a class of `submit`
|
||||
5. Go into the CSS file and add `display: flex` to the footer. Also add `flex-grow:1` to the span inside of the footer
|
||||
|
||||
> Hint: Look back at the CSS demo to see the various ways you can use selectors to target existing HTML
|
||||
|
|
|
@ -99,11 +99,17 @@ In CSS-based styling, visual states are applied by adding and removing classes.
|
|||
|
||||
#### Adding a Controlled Input
|
||||
|
||||
In traditional HTML forms, users interact with the form, and on submit, those values are captured and transmitted. Those are called **uncontrolled inputs**.
|
||||
In React, form elements such as `<input>`, `<textarea>`, and `<select>` can be used as either **uncontrolled** or **controlled**. (This paradigm also applies to UI Fabric's customized implementations of form components, which we'll use later.)
|
||||
|
||||
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 makes forms much easier to work with.
|
||||
An **uncontrolled input** maintains its current value internally and updates it based on user interactions (entering text, choosing options, etc). The code only pulls the value from the input when it's needed, such as on submit. This is similar to how inputs in a plain HTML form work.
|
||||
|
||||
To create a controlled component, we need two things, which our demo already provides:
|
||||
A **controlled input** takes its current value from a prop and use a callback to notify the parent component of changes made by the user. The input's value doesn't change until the parent component updates the input's props in response to the callback.
|
||||
|
||||
Typically, a controlled input's current value is stored in the parent component's state (then passed to the input as a prop during render). The parent updates its state in response to the callback, which causes the input to be re-rendered with a new prop value. This round trip process might sound inefficient, but in reality it has little to no impact and helps enable some advanced form functionality.
|
||||
|
||||
> The distinction between controlled and uncontrolled is important to understand when writing or using form components, and misunderstandings of this concept are a very common source of bugs. See [this article](https://goshakkk.name/controlled-vs-uncontrolled-inputs-react/) for a more detailed explanation.
|
||||
|
||||
To add a controlled input, we need two things, which our demo already provides:
|
||||
|
||||
1. A state variable to hold the input's value:
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
### 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`
|
||||
1. Use the provided `itemCount` value to display the current number of items left.
|
||||
2. Use a ternary operator to print "item" vs "item**s**" based on whether `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?
|
||||
> Note that this is only half the work we need to do to make these controlled inputs work. What is the other half?
|
||||
|
|
|
@ -3,10 +3,11 @@ import React from 'react';
|
|||
export class TodoListItem extends React.Component<any, any> {
|
||||
render() {
|
||||
const { label, completed } = this.props;
|
||||
// The "no-op" onChange handler prevents a console warning from React at runtime
|
||||
return (
|
||||
<li className="todo">
|
||||
<label>
|
||||
<input type="checkbox" checked={completed} /> {label}
|
||||
<input type="checkbox" checked={completed} onChange={() => undefined} /> {label}
|
||||
</label>
|
||||
</li>
|
||||
);
|
||||
|
|
|
@ -1,34 +1,34 @@
|
|||
# Types and Creating a UI Driven State
|
||||
|
||||
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.
|
||||
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`. Values from the state are then passed down to the UI as props.
|
||||
|
||||
> We'll be learning in part 2 of this workshop how we can expose these functions without explicitly passing them down via props
|
||||
> 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` and passed into our components.
|
||||
This is our core "business logic" and handles everything our basic "CRUD" operations: 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` and passed into our components.
|
||||
|
||||
## 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.
|
||||
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, and 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 loosely 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.
|
||||
As applications grow, it becomes increasing difficult to remember what each function does, or what each todo contains. Also, as JavaScript is a loosely typed 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.
|
||||
It for these two reasons that the entire industry is shifting to writing applications that are strongly typed, and many are using TypeScript to accomplish that.
|
||||
|
||||
As [their website](https://www.typescriptlang.org/) state:
|
||||
As [TypeScript's website](https://www.typescriptlang.org/) states:
|
||||
|
||||
> TypeScript is a superset of JavaScript that compiles to plain JavaScript
|
||||
> 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 writing in `ts` and `tsx` files instead of `js` and `jsx` files.
|
||||
If you've ever used [Sass](https://sass-lang.com/) you are familiar with this concept. In the same way that all valid CSS is valid Sass, all valid JavaScript is valid TypeScript. That's why most of these exercises have been written 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.
|
||||
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. There isn't any actionable UI in this component as we're simply passing `completed` down to each `TodoListItem`, but we can write a component interface to make sure that everything gets passed down properly.
|
||||
Let's start off in the TodoList, as that has the most data flow up and down. There isn't any interactive UI in this component, as we're simply passing `completed` down to each `TodoListItem`, but we can write a props interface for the component to make sure that everything gets passed down properly.
|
||||
|
||||
## Writing TodoListProps
|
||||
|
||||
Looking at our `TodoApp` we know that `TodoList` has three props, `filter`, `todos`, and `complete`. We'll start by creating an interface that represents this component's props called `TodoListProps`.
|
||||
Looking at our `TodoApp` we know that `TodoList` has three props: `filter`, `todos`, and `complete`. We'll start by creating an interface called `TodoListProps` that represents this component's props.
|
||||
|
||||
```tsx
|
||||
interface TodoListProps {
|
||||
|
@ -38,7 +38,7 @@ interface TodoListProps {
|
|||
}
|
||||
```
|
||||
|
||||
> 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.
|
||||
> Note that we're using the `any` keyword for now. This won't give us any type safety, but it does let us specify valid prop names we can pass to this component.
|
||||
|
||||
With that interface written, we'll add it to our component class.
|
||||
|
||||
|
@ -46,17 +46,17 @@ With that interface written, we'll add it to our component class.
|
|||
export class TodoList extends React.Component<TodoListProps, any>
|
||||
```
|
||||
|
||||
> Note that the first value in `<>` is for a props interface, and the second for state
|
||||
> Note that the first value in `<>` is for a props interface, and the second is 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.
|
||||
So far we've only established what our prop names are, not the type of 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:
|
||||
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 {
|
||||
|
@ -66,7 +66,7 @@ interface TodoListProps {
|
|||
}
|
||||
```
|
||||
|
||||
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):
|
||||
But since we know that the filter can be only one of three values, we can make that explicit with a [union type](https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types):
|
||||
|
||||
```tsx
|
||||
interface TodoListProps {
|
||||
|
@ -76,11 +76,11 @@ interface TodoListProps {
|
|||
}
|
||||
```
|
||||
|
||||
Now try going back to `TodoApp` and changing the `filter` attribute in `TodoList` to something else.
|
||||
Now try going back to `TodoApp` and changing the `filter` attribute in `TodoList` to something else. You'll see an error in the editor (if using VS Code) and on the command line when you save the file.
|
||||
|
||||
### Complete Type
|
||||
|
||||
The `complete` props isn't data, but rather a function. Fortunately, TypeScript can handle function types just as well as data.
|
||||
The `complete` prop isn't data, but a function. Fortunately, TypeScript can handle function types just as well as data.
|
||||
|
||||
```tsx
|
||||
interface TodoListProps {
|
||||
|
@ -90,7 +90,9 @@ interface TodoListProps {
|
|||
}
|
||||
```
|
||||
|
||||
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.
|
||||
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 returned value.
|
||||
|
||||
> Technically, all functions in JavaScript return `undefined` if no other return value is specified, but declaring a return type of `void` causes TypeScript to error if you try to return a value from the function (or use its default returned value of `undefined`).
|
||||
|
||||
## Todos Type
|
||||
|
||||
|
@ -109,13 +111,13 @@ interface TodoListProps {
|
|||
}
|
||||
```
|
||||
|
||||
> 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.
|
||||
> Note that `[id: string]` does not indicate an array; it is an object [index signature](https://www.typescriptlang.org/docs/handbook/interfaces.html#indexable-types).
|
||||
|
||||
Now that our interface is complete, try changing the word 'all' in `filter === all` and see that VS Code will tell you this condition will always be false. Imagine you had a typo in that line and you couldn't understand why your filter wasn't working.
|
||||
Now that our interface is complete, try changing the word "all" in `filter === all` and see that VS Code will tell you this condition will always be false. Compare this to plain JavaScript: if you had a typo in that line, you wouldn't understand why your filter wasn't working.
|
||||
|
||||
## Abstracting types
|
||||
## Sharing types
|
||||
|
||||
Most of our components are going to need to add types for `todos` and `filter`, so it's a good thing that TypeScript allows us to abstract those. I've already written up and exported those shared types in the file `TodoApp.types.ts`, so we just need to import them and pull them into our interface.
|
||||
Most of our components will need to specify types for `todos` and `filter`, so it's a good thing that TypeScript allows us to share types between files. I've already written up and exported those shared types in the file `TodoApp.types.ts`, so we just need to import them and use them in our interface.
|
||||
|
||||
```tsx
|
||||
import { FilterTypes, Todos } from '../TodoApp.types';
|
||||
|
@ -131,17 +133,17 @@ interface TodoListProps {
|
|||
|
||||
Our `TodoApp` doesn't take any props, but it does have state. We can use TypeScript to define that as well.
|
||||
|
||||
I've already imported `Todos`, and `FilterTypes` into the `TodoApp`, so we just need to add them to our class. We can even skip the 'interface', if we want to, and add them directly to the class.
|
||||
I've already imported `Todos` and `FilterTypes` into the `TodoApp`, so we just need to add them to our class. If we want, we can even skip a separate interface definition and just declare the type inline. (This is not recommended for types of any complexity or types that are used in multiple places.)
|
||||
|
||||
```tsx
|
||||
export class TodoApp extends React.Component<{}, { todos: Todos; filter: FilterTypes }>
|
||||
```
|
||||
|
||||
> Note that the first value in `<>` always refers to props. Since `TodoApp` takes none, we'll set it to an empty object.
|
||||
> Note that the first value in `<>` always refers to props. Since `TodoApp` takes none, we'll set it to an empty object type.
|
||||
|
||||
## Writing TodoListItemProps
|
||||
|
||||
Jumping down to the TodoListItem, as we start to write the TodoListItemProps we realize that two of the props, `label` and `completed` have already been defined in the `TodoItem` interface in `TodoApp.types`. So in the same way we can reuse individual types (`FilterTypes`), and extend upon entire interfaces.
|
||||
Jumping down to the TodoListItem, as we start to write the `TodoListItemProps` we realize that two of the props, `label` and `completed`, have already been defined in the `TodoItem` interface in `TodoApp.types`. So we can make `TodoListItemProps` reuse the `TodoItem` interface by extending it.
|
||||
|
||||
```tsx
|
||||
interface TodoListItemProps extends TodoItem {
|
||||
|
@ -150,17 +152,17 @@ interface TodoListItemProps extends TodoItem {
|
|||
}
|
||||
```
|
||||
|
||||
The end result of this is an interface with all 4 properties, `id`, `complete`, `completed` and `label`.
|
||||
The end result of this is an interface with all four properties: `id`, `complete`, `completed` and `label`.
|
||||
|
||||
Next we can pull in the remaining props:
|
||||
Next we can pull in the remaining props in the render function:
|
||||
|
||||
```jsx
|
||||
const { label, completed, complete, id } = this.props;
|
||||
```
|
||||
|
||||
And then use the input's `onChange` event to fire our `complete` callback. We can see in the signature that we expect and `id` of type string, so we'll pass our `id` prop in.
|
||||
And then use the input's `onChange` event to fire our `complete` callback. We can see in the signature that `complete` expects an `id` of type string, so we'll pass our `id` prop in.
|
||||
|
||||
> A [callback function](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function) is a function passed into a component as a prop
|
||||
> A [callback function](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function) is a function passed into a component as a prop.
|
||||
|
||||
```tsx
|
||||
<input type="checkbox" checked={completed} onChange={() => complete(id)} />
|
||||
|
@ -168,4 +170,4 @@ And then use the input's `onChange` event to fire our `complete` callback. We ca
|
|||
|
||||
> Note that the function param and prop name just happen to be the same. This isn't required.
|
||||
|
||||
Now that our todos are firing the `onChange` callback, give them a click and take look at how the app response. Since our footer text is driven off of the number of unchecked todos, the footer will automatically update to reflect the new state.
|
||||
Now that our todos are firing the `onChange` callback, give them a click and take look at how the app responds. Since our footer text is based on the number of unchecked todos, the footer will automatically update to reflect the new state.
|
||||
|
|
|
@ -6,7 +6,7 @@ export const TodoFooter = (props: any) => {
|
|||
return (
|
||||
<footer>
|
||||
<span>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</span>
|
||||
<button className="submit">Clear Completed</button>
|
||||
</footer>
|
||||
|
|
|
@ -8,7 +8,7 @@ export class TodoListItem extends React.Component<any, any> {
|
|||
return (
|
||||
<li className="todo">
|
||||
<label>
|
||||
<input type="checkbox" checked={completed} /> {label}
|
||||
<input type="checkbox" checked={completed} onChange={() => undefined} /> {label}
|
||||
</label>
|
||||
</li>
|
||||
);
|
||||
|
|
|
@ -2,19 +2,26 @@
|
|||
|
||||
### TodoFooter
|
||||
|
||||
1. Open TodoFooter and write a TodoFooterProps interface. It should include two values, a function and an object. Assign this interface to props like this: `(props: TodoFooterProps)`
|
||||
1. Open TodoFooter and write a TodoFooterProps interface. It should include two values, a function and an object. Use this interface in the function props like this: `(props: TodoFooterProps)`
|
||||
|
||||
2. Write an `_onClick` function that calls `props.clear`.
|
||||
> Since TodoFooter is not a class the `_onClick` needs to be declared as a const, and placed before the `return`.
|
||||
3. Add `_onClick` to the button's `onClick`. You won't need to use `this` since this isn't a class.
|
||||
> We can't assign our `clear` function directly to `onClick`. We always need to create a function that calls our callbacks. `() => props.clear()`
|
||||
4. Test out this functionality. Check a few todos complete and click the `Clear Completed` button
|
||||
> Since TodoFooter is not a class, the `_onClick` function needs to be stored in a const placed before the `return`.
|
||||
|
||||
3. Assign `_onClick` to the button's `onClick` prop. You won't need to use `this` since the component isn't a class.
|
||||
|
||||
4. Test out this functionality. Check a few todos complete and click the `Clear Completed` button.
|
||||
|
||||
### TodoHeader
|
||||
|
||||
1. Open TodoHeader and write TodoHeaderProps which will include 3 values. Replace the first `any` with this interface.
|
||||
2. This component also has state. Write TodoHeaderState (there's just one item), and add this where the second `any` was.
|
||||
1. Open TodoHeader and write TodoHeaderProps which will include three values. Replace the first `any` in the class declaration with this interface.
|
||||
|
||||
2. This component also has state. Write TodoHeaderState (there's just one value), and add this where the second `any` was.
|
||||
|
||||
3. Add `_onFilter` to each of the filter buttons
|
||||
> Note that we can't add new parameters to onClick, but we can pull information from the event target!
|
||||
|
||||
4. Write an `_onAdd` method that calls `addTodo` on the current `labelInput`, then sets the `labelInput` in state to an empty string
|
||||
|
||||
5. Call `_onAdd` from the submit button
|
||||
|
||||
6. Check out this new functionality! We can now add and filter todos!
|
||||
|
|
|
@ -6,7 +6,7 @@ export const TodoFooter = props => {
|
|||
return (
|
||||
<footer>
|
||||
<span>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</span>
|
||||
<button className="submit">Clear Completed</button>
|
||||
</footer>
|
||||
|
|
|
@ -14,7 +14,7 @@ export const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<footer>
|
||||
<span>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</span>
|
||||
<button onClick={_onClick} className="submit">
|
||||
Clear Completed
|
||||
|
|
|
@ -13,7 +13,7 @@ export const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
|
@ -6,7 +6,7 @@ export const TodoFooter = (props: any) => {
|
|||
return (
|
||||
<footer>
|
||||
<span>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount <= 1 ? '' : 's'} left
|
||||
</span>
|
||||
<button onClick={() => props.clear()} className="button">
|
||||
Clear Completed
|
||||
|
|
|
@ -13,7 +13,7 @@ export const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
|
@ -34,7 +34,7 @@ export class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState
|
|||
styles={props => ({
|
||||
...(props.focused && {
|
||||
field: {
|
||||
backgroundColor: 'black'
|
||||
backgroundColor: '#c7e0f4'
|
||||
}
|
||||
})
|
||||
})}
|
||||
|
|
|
@ -16,7 +16,7 @@ export const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
|
@ -15,7 +15,7 @@ const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
|
@ -16,7 +16,7 @@ export const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
|
@ -15,7 +15,7 @@ const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
|
@ -29,7 +29,7 @@ class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState> {
|
|||
|
||||
<Stack horizontal gap={10}>
|
||||
<Stack.Item grow>
|
||||
<TextField placeholder="What needs toasdf be done?" value={this.state.labelInput} onChange={this.onChange} />
|
||||
<TextField placeholder="What needs to be done?" value={this.state.labelInput} onChange={this.onChange} />
|
||||
</Stack.Item>
|
||||
<PrimaryButton onClick={this.onAdd}>Add</PrimaryButton>
|
||||
</Stack>
|
||||
|
|
|
@ -15,7 +15,7 @@ const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
|
@ -15,7 +15,7 @@ const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
|
@ -15,7 +15,7 @@ const TodoFooter = (props: TodoFooterProps) => {
|
|||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
|
|
Загрузка…
Ссылка в новой задаче