Modifying the ReactTodo component

Next, create a new file in the components folder, name it IReactTodoState.ts, and copy the following code to the file and save it:

import {ITodoItem} from "../ITodoItem"; 
export interface IReactTodoState { 
    todoItems?: ITodoItem[]; 
    showNewTodoPanel?: boolean; 
    newItemTitle?: string; 
    newItemDone?: boolean; 
} 

Here, we declare all objects and variables that we are using to describe the state of our ReactTodo component. Note that all of them are marked as optional with a question mark. When we change the state of our component with the this.setState function, we don't always want to change all of the values, and by making them optional, TypeScript allows us to change just one or a few of them.

In the interface that is used to describe the state of ReactTodo component, the most important property is todoItems, into which we will save the array of to-do items. Then, we have three properties to handle a new to-do item, as you will soon see.

Now, it is time to open ReactTodo.tsx. This file will define our main React component, which is loaded in the web part file. First, add the following import statements, and as you can see, there are quite many of them:

import { ITodoItem } from '../ITodoItem'; 
import { IReactTodoState } from './IReactTodoState'; 
import TodoItemComponent from './TodoItemComponent'; 
import { CommandButton, PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button'; 
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel'; 
import { Toggle } from 'office-ui-fabric-react/lib/Toggle'; 
import { TextField } from 'office-ui-fabric-react/lib/TextField'; 

We first import ITodoItem, which is our data model, and IReactTodoState, which we just declared, to be able to save the state of our component. Then, we import another component called TodoItemComponent, which represents a single to-do item. The rest of the imports are Office UI Fabric components.

Handling state is an essential part of building React solutions. For the ReactTodo component, we start by adding a constructor for the class. Add the following code inside the class definition:

constructor(props: IReactTodoProps) { 
  super(props); 
  this.state = { 
    todoItems: [], 
    showNewTodoPanel: false 
  }; 
  this.props.todoClient.getItems() 
  .then((resolvedTodoItems) => { 
    this.setState({ 
      todoItems: resolvedTodoItems 
    }); 
  }); 
} 

Whenever you are doing React component constructors, first call super(props). Then, set the state's initial values. At the time of an object instantiation, todoItems will be an empty array and showNewTodoPanel, the boolean variable controlling the visibility of the panel with a UI to add a new item, is false. The only place in a React component where you can, and should, set the state directly is the constructor function.

It might seem odd, but just after we set the state directly, we will set the value of this.state.todoItems asynchronously. We do this to make sure that we have an empty array of items when the render function is called initially by React. When then happens, we will use the setState function to change the value of todoItems. When the state changes, React will call the render function again automatically.

Next, replace the existing render function with the following code:

public render(): React.ReactElement<IReactTodoProps> { 
  const items: any[] = []; 
  this.state.todoItems.forEach((todoItem: ITodoItem) => { 
    items.push( 
      <li> 
        <TodoItemComponent 
          itemId={todoItem.Id} 
          itemTitle={todoItem.Title} 
          itemDone={todoItem.Done} 
          edit={this.edit.bind(this)} 
          delete={this.delete.bind(this)}> 
        </TodoItemComponent> 
      </li> 
    ); 
  }); 
  return ( 
    <div className={styles.reactTodo}> 
        <div className={styles.container}> 
          <div className="ms-font-xxl">To-do list</div> 
          <CommandButton 
            iconProps={{iconName:"Add"}} 
            onClick={this.addItem.bind(this)}> 
            Add new To-do 
          </CommandButton> 
          <Panel 
              isOpen={this.state.showNewTodoPanel} 
              type={PanelType.smallFixedFar} 
              onDismiss={this.closeNewTodoPanel.bind(this)} 
              headerText="Add new To-do" 
              onRenderFooterContent={ () => { 
              return ( 
                <div> 
                  <PrimaryButton 
                    onClick={this.saveNewTodo.bind(this)} 
                    style={ { 'marginRight': '8px' } } > 
                    Save 
                  </PrimaryButton> 
                  <DefaultButton 
                    onClick={this.closeNewTodoPanel.bind(this)}> 
                    Cancel 
                  </DefaultButton> 
                </div> 
              ); 
            } }> 
            <TextField 
              label="Title" 
              underlined 
              placeholder="Give your to-do a title" 
              value={this.state.newItemTitle} 
              onChanged={this.newItemTitleChange.bind(this)}  /> 
            <Toggle 
              label="Done" 
              checked={this.state.newItemDone} onChanged={this.toggleNewItemDone.bind(this)} /> 
          </Panel>   
          <ul className={styles.todoList}> 
            {items} 
          </ul>         
        </div> 
      </div> 
  ); 
} 

The render function starts by building an array of to-do items, as follows:

Each item is enclosed in an HTML <li></li>, and inside them is TodoItemComponent, which we will be building later on. We will pass props for each to-do item component and bind two props to ReactTodo component's functions to lift the state from the child component to parent.

Let's study the return statement of the render function in the following statement:

In our return statement, we have a title element in line 85 and Add new To-do command button in lines 86-90. By clicking on a button a function is called, and it will change the state so that the add new to-do panel in lines 91-120 will be visible. Lastly, in lines 121-123, we render the list of to-do items to the user.

The add new to-do panel is a bit longer, but not too complicated. We declared some properties such as the header text and footer content with two buttons. The important part is to understand how we bind the functions of this component to the child component's props. When a user interacts with the Fabric React component, our function will handle the situation. Consider the following code snippet:

The actual contents of the panel are declared inside the <Panel> element:

TextField is used to get a user input for the title of the to-do item, and Toggle is used to ask the user whether the to-do item has been done already.

Finally, add the following code that includes the bind functions:

private addItem(): void { 
  this.setState({showNewTodoPanel: true}); 
} 
private closeNewTodoPanel(): void { 
  this.setState({ 
    showNewTodoPanel: false, 
    newItemDone: false, 
    newItemTitle: "" 
  }); 
} 
private saveNewTodo(): void { 
  this.props.todoClient 
    .add(this.state.newItemTitle, this.state.newItemDone).then(() => { 
      this.setState({newItemDone: false, newItemTitle: "", showNewTodoPanel: false}); 
      this.refreshTodoItems(); 
    }); 
} 
private newItemTitleChange(value: string): void { 
  this.setState({newItemTitle: value}); 
} 
private toggleNewItemDone(e: any): void { 
  this.setState((prevState, props) => ({ 
    newItemDone: !prevState.newItemDone 
  })); 
} 
private delete(Id: number): void { 
  this.props.todoClient.delete(Id); 
  this.refreshTodoItems(); 
} 
private edit(TodoItem: ITodoItem): void { 
  this.props.todoClient.edit(TodoItem); 
  this.refreshTodoItems(); 
} 
public refreshTodoItems(): void { 
  this.props.todoClient.getItems().then((resolvedTodoItems: ITodoItem[]) => { 
    this.setState({ 
      todoItems: resolvedTodoItems 
    }); 
  }); 
} 

The addItem function changes the component state by setting the value of this.state. showNewTodoPanel to true, which means that the add new to-do item panel will be rendered visible. The closeNewTodoPanel function will do the opposite--it will hide the panel, but it will also clear the values for newItemTitle and newItemDone so that closing the panel will also clear the user input.

The saveNewTodo function, which is called when the Save button of the new to-do item panel is called, uses the TodoClient object that is in props of this class to call the add function of the client and save data to the to-do list.

This is lifting the state in React. After that, we clear the state related to the new to-do item and call our helper function refreshTodoItems to retrieve a current array of todoItems and set it to the state.

The newItemTitleChange and toggleNewItemDone functions are bound to TextField and Toggle components in the new to-do item panel. They will take the user input and set state to match it.

Then, we will declare delete and edit functions that we passed as props for TodoItemComponent. That component will use these functions to lift the state to its parent, the ReactTodo component.

Open ReactTodoModule.scss and add the following CSS fragment after .container element; the SCSS file extension stands for Sassy CSS, which allows us to write smart syntax close to CSS to avoid the usual complexity that comes with larger CSS files:

.todoList { 
  list-style-type: none; 
} 

Our to-do items are rendered as an HTML list, and this will remove the bullet point from the start of each list item.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.83.223