[React family bucket] through the component design mode, making simple todo (including source code)

Posted by FijiSmithy on Sun, 23 Jan 2022 12:42:51 +0100

1, Design project structure

Because we implement todo as a functional component, the basic design idea is:

1. Create a todo folder in components and a new index JSX is a todo component. This folder contains some todo widgets

2. On app Import todo from 'is introduced into JSX/ Components / todo 'and render in render:

import React, { Component } from 'react';
import Todo from './components/Todo';

export default class APP extends Component {
  render() {
    return (
      <div id="root">
        <Todo />
      </div>
    );
  }
}

2, Split components to realize static pages

1. We split the todo component into four parts: Header, List, Item and Footer

2. In todo folder, create four folders: Header, List, Item and Footer, and create index JSX and index css

3. Fill the static resources into jsx and css respectively

Todo folder

/*Todo/index.jsx*/
import React, { Component } from 'react';
import './index.css'
import Footer from './Footer';
import Header from './Header';
import List from './List';

export default class Todo extends Component {
  render() {
    return (
      <div id="root">
        <div className="todo-container">
          <div className="todo-wrap">
            <Header />
            <List />
            <Footer />
          </div>
        </div>
      </div>
    );
  }
}
/*Todo/index.css--Common style*/
body {
    background: #fff;
  }
  
  .btn {
    display: inline-block;
    padding: 4px 12px;
    margin-bottom: 0;
    font-size: 14px;
    line-height: 20px;
    text-align: center;
    vertical-align: middle;
    cursor: pointer;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
    border-radius: 4px;
  }
  
  .btn-danger {
    color: #fff;
    background-color: #da4f49;
    border: 1px solid #bd362f;
  }
  
  .btn-danger:hover {
    color: #fff;
    background-color: #bd362f;
  }
  
  .btn:focus {
    outline: none;
  }
  
  .todo-container {
    width: 600px;
    margin: 0 auto;
  }
  .todo-container .todo-wrap {
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
  }
  

Header folder

/*Header/index.jsx*/

import React, { Component } from 'react';
import './index.css'

export default class Header extends Component {
  render() {
    return (
      <div className="todo-header">
        <input type="text" placeholder="Please enter your task name and press enter to confirm" />
      </div>
    )
  }
}

/*header/index.css*/
.todo-header input {
    width: 560px;
    height: 28px;
    font-size: 14px;
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 4px 7px;
}

.todo-header input:focus {
    outline: none;
    border-color: rgba(82, 168, 236, 0.8);
    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}

List folder

/*List/index.jsx*/
import React, { Component } from 'react';
import Item from '../Item';
import './index.css'

export default class List extends Component {
  render() {
    return (
      <ul className="todo-main">
        <Item />
      </ul>
    );
  }
}
/*List/index.css*/
.todo-main {
    margin-left: 0px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding: 0px;
}

.todo-empty {
    height: 40px;
    line-height: 40px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding-left: 5px;
    margin-top: 10px;
}

Item Folder

/*Item/index.jsx*/
import React, { Component } from 'react';
import './index.css'

export default class Item extends Component {
  render() {
    return (
      <div>
        <li>
          <label>
            <input type="checkbox" />
            <span></span>
          </label>
          <button className="btn btn-danger" style={{ display: 'none' }}>delete</button>
        </li>
      </div>
    );
  }
}

/*Item/index.css*/
li {
  list-style: none;
  height: 36px;
  line-height: 36px;
  padding: 0 5px;
  border-bottom: 1px solid #ddd;
}

li label {
  float: left;
  cursor: pointer;
}

li label li input {
  vertical-align: middle;
  margin-right: 6px;
  position: relative;
  top: -1px;
}

li button {
  float: right;
  display: none;
  margin-top: 3px;
}

li:before {
  content: initial;
}

li:last-child {
  border-bottom: none;
}

Footer folder

/*Footer/index.jsx*/
import React, { Component } from 'react';
import './index.css'

export default class Footer extends Component {
  render() {
    return (
      <div className="todo-footer">
        <label>
          <input type="checkbox" />
        </label>
        <span>
          <span>Completed 0</span> / All 2
        </span>
        <button className="btn btn-danger">Clear completed tasks</button>
      </div>
    );
  }
}

/*Footer/index.css*/
.todo-footer {
    height: 40px;
    line-height: 40px;
    padding-left: 6px;
    margin-top: 5px;
}

.todo-footer label {
    display: inline-block;
    margin-right: 20px;
    cursor: pointer;
}

.todo-footer label input {
    position: relative;
    top: -1px;
    vertical-align: middle;
    margin-right: 5px;
}

.todo-footer button {
    float: right;
    margin-top: 5px;
}

3, Function 1_ Add task information to the first line

Because the value transfer between sibling components is involved, we need to pass the Props value transfer method with the parent component

1. First, set the analog number in the parent component (Todo/index.jsx) and transfer it to the child component List

/*Todo/index.jsx*/
import React, { Component } from 'react';
import './index.css'
import Footer from './Footer';
import Header from './Header';
import List from './List';

export default class APP extends Component {
  state = {
    todos: [{
      id: '001',
      name: 'having dinner',
      done: true
    }, {
      id: '002',
      name: 'sleep',
      done: true
    }]
  }
  render() {
    const { todos } = this.state
    return (
      <div id="root">
        <div className="todo-container">
          <div className="todo-wrap">
            <Header />
            <List todos={todos} />
            <Footer />
          </div>
        </div>
      </div>
    );
  }
}

2. The List component receives the Props from the parent component Todo, and transmits the value to the sub component Item of the List for display

/*List/index.jsx*/
import React, { Component } from 'react';
import Item from '../Item';
import './index.css'

export default class List extends Component {
  render() {
    const { todos } = this.props;
    return (
      <ul className="todo-main">
        {
          todos.map((item) => {
            return <Item key={item.id} {...item} />
          })
        }
      </ul>
    );
  }
}

3. The Item component receives the Props from the parent component List and displays them

import React, { Component } from 'react';
import Item from '../Item';
import './index.css'

export default class List extends Component {
  render() {
    const { todos } = this.props;
    return (
      <ul className="todo-main">
        {
          todos.map((item) => {
            return <Item key={item.id} {...item} />
          })
        }
      </ul>
    );
  }
}

4. The parent component Todo obtains the data added to the child component Header by means of Props transfer function (addTodo)

import React, { Component } from 'react';
import './index.css'
import Footer from './Footer';
import Header from './Header';
import List from './List';

export default class APP extends Component {
  state = {
    todos: [{
      id: '001',
      name: 'having dinner',
      done: true
    }, {
      id: '002',
      name: 'sleep',
      done: true
    }]
  }
  //It is used to add a todo. The parameter is todo object
  addTodo = (todoObj) => {
    let { todos } = this.state;
    //Put the input first in the array
    todos.unshift(todoObj);
    //modify state
    this.setState(todos)
  }
  render() {
    const { todos } = this.state
    return (
      <div id="root">
        <div className="todo-container">
          <div className="todo-wrap">
            <Header addTodo={this.addTodo} />
            <List todos={todos} />
            <Footer />
          </div>
        </div>
      </div>
    );
  }
}

5. The Header component executes the function passed from the parent component Todo by listening to the "Enter" key to update the status

nanoid is used to generate unique values, mainly to assign values to the key s of elements

import React, { Component } from 'react';
//Similar to uuid, a unique id is generated
import { nanoid } from 'nanoid';
import './index.css'

export default class Header extends Component {
  handleKeyUp = (event) => {
    const { keyCode, target } = event;
    if (keyCode === 13) {
      //Here, you also need to judge various cases of spaces (regular)
      if (target.value !== "") {
        const newTodo = { id: nanoid(), name: target.value, done: false };
        this.props.addTodo(newTodo);
        //Clear the input
        target.value = ""
      }
    }
  }
  render() {
    return (
      <div className="todo-header">
        <input type="text" placeholder="Please enter your task name and press enter to confirm" onKeyUp={this.handleKeyUp} />
      </div>
    )
  }
}

4, Function 2_ Moving the mouse over the list will highlight and display the "delete" button

This function is mainly judged by the move in and move out events of the mouse. The highlighted function can be realized through: hover in css. This case uses the method in React

1. Define component status

state = { mouseType: false }

2. Define mouse move in and move out events. true indicates move in and false indicates move out; And the backgroundColor and display are rendered according to the mouseType value in the state. (the Coriolis method of higher-order function is used here)

import React, { Component } from 'react';
import './index.css'

export default class Item extends Component {
  state = { mouseType: false }
  //Mouse in and out event
  handleMouse = (mouseType) => {
    return () => {
      this.setState({ mouseType })
    }
  }
  render() {
    const { name, done } = this.props
    return (
      <div>
        <li style={{ backgroundColor: this.state.mouseType ? '#ddd' : 'white' }} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
          <label>
            <input type="checkbox" defaultChecked={done} />
            <span>{name}</span>
          </label>
          <button className="btn btn-danger" style={{ display: this.state.mouseType ? 'block' : 'none' }}>delete</button>
        </li>
      </div>
    );
  }
}

5, Function 3_ Change done in state by clicking the check box

1. Because to modify the state in the parent component Todo of the parent component List by the child component Item, you need to define the updateTodo function in the parent component Todo and pass it to the Item component through props

/*Todo/index.jsx*/

//Used to update todo. The parameter is id
  updateTodo = (id, done) => {
    const { todos } = this.state;
    let newTodos = todos.map((item) => {
      if (item.id === id) {
        return { ...item, done }
      } else {
        return item
      }
    })
    this.setState({ todos: newTodos })
  }
<List todos={todos} updateTodo={this.updateTodo} />

List/index.jsx

<Item key={item.id} {...item} updateTodo={updateTodo} />

2. In the Item component, add an onChange event to the check box, and pass the id of todo corresponding to the currently selected check box as a parameter to handleCheck

<input type="checkbox" defaultChecked={done} onChange={this.handleCheck(id)} />

3. In the handleCheck function, execute the id and checked of the current check box as parameters of updateTodo

//Check or uncheck
handleCheck = (id) => {
  return (event) => {
    this.props.updateTodo(id, event.target.checked)
  }
}

4. The prop types library is introduced to limit the props of each component (see the following for the usage method: npm.com prop-types)

  /*With item / index JSX as an example*/
  //Limit props
  static propTypes = {
    updateTodo: PropTypes.func.isRequired,
    done: PropTypes.bool.isRequired
  }

6, Function 4_ Delete a todo

1. The logic is the same as modifying done. First, bind onClick to the delete button in the Item component

<button onClick={this.handleDel(id)} className="btn btn-danger" style={{ display: this.state.mouseType ? 'block' : 'none' }}>delete</button>

2. Define the handleDel function and execute the deleteTodo function passed from the Todo component

  //Delete a todo
  handleDel = (id) => {
    return () => {
      this.props.deleteTodo(id)
    }
  }

3. The deleteTodo function is defined in the Todo component and passed to the sub component List, and then passed to the sub component Item through the List component

Todo/index.jsx

//Deletes the specified todo object
  deleteTodo = (id) => {
    const { todos } = this.state;
    const newTodos = todos.filter(item => {
      return item.id !== id
    })
    this.setState({ todos: newTodos })
  }
<List todos={todos} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo} />

List/index.jsx

<Item key={item.id} {...item} updateTodo={updateTodo} deleteTodo={deleteTodo} />

7, Function 5_ Realize the function of selecting all and clearing at the bottom

1. First, the statistics of completed data and all data are realized

Footer/index.jsx

const { todos } = this.props
//Completed
const doneCount = todos.reduce((pre, current) => {
  return current.done === true ? ++pre : pre
}, 0)
<span>
  <span>Completed{doneCount}</span> / whole{todos.length}
</span>

2. Realize the function of selecting all

Footer/index.jsx

<input type="checkbox" checked={doneCount === todos.length && todos.length !== 0 ? true : false} onChange={this.handleCheckAll} />
//Select all
handleCheckAll = (event) => {
	this.props.checkAllTodo(event.target.checked)
}

3. Because the data state needs to be changed, the function should be defined in the parent component Todo and passed through props

Todo/index.jsx

//Select all
checkAllTodo = (done) => {
    const { todos } = this.state;
    let newTodos = todos.map(item => {
      return { ...item, done: done }
    })
    this.setState({ todos: newTodos })
}
<Footer todos={todos} checkAllTodo={this.checkAllTodo} />

4. Realize the function of clearing completed

Footer/index.jsx

<button className="btn btn-danger" onClick={this.handleClear}>Clear completed tasks</button>
//Clear completed tasks
handleClear = () => {
	this.props.clearAllDone()
}

5. Because the data state needs to be changed, the function should be defined in the parent component Todo and passed through props

Todo/index.jsx

//Clear all completed
clearAllDone = () => {
    const { todos } = this.state;
    const newTodos = todos.filter(item => {
      return item.done === false
    })
    this.setState({ todos: newTodos })
}
<Footer todos={todos} checkAllTodo={this.checkAllTodo} clearAllDone={this.clearAllDone} />

8, Case summary

1. Split components and implement static components. Note: the writing of className and style

2. How to dynamically initialize the list and determine which component's state to put the data in

Use of a component: put it in its own state

Some components use: put them in their common parent component state (officially called state promotion)

3. About communication between father and son:

(1) the [parent component] transfers data to the [child component] through props

(2) data transfer from [sub component] to [parent component]: transfer through props, requiring [parent component] to transfer a function to [sub component] in advance

4. Note the difference between deafaultChecked and Checked. Similarly, there are defaultValue and Value

5. Because the data state needs to be changed, the function should be defined in the parent component Todo and passed through props

Todo/index.jsx

//Clear all completed
clearAllDone = () => {
    const { todos } = this.state;
    const newTodos = todos.filter(item => {
      return item.done === false
    })
    this.setState({ todos: newTodos })
}
<Footer todos={todos} checkAllTodo={this.checkAllTodo} clearAllDone={this.clearAllDone} />

8, Case summary

1. Split components and implement static components. Note: the writing of className and style

2. How to dynamically initialize the list and determine which component's state to put the data in

Use of a component: put it in its own state

Some components use: put them in their common parent component state (officially called state promotion)

3. About communication between father and son:

(1) the [parent component] transfers data to the [child component] through props

(2) data transfer from [sub component] to [parent component]: transfer through props, requiring [parent component] to transfer a function to [sub component] in advance

4. Note the difference between deafaultChecked and Checked. Similarly, there are defaultValue and Value

5. Where the state is, the method of operating the state is

Source address: https://gitee.com/daiwanghao/react-family-bucket.git

For the above content of todo produced by React, please pay attention to the column of React family bucket.
I will share with you the common problems in my usual projects and the knowledge of written examination and interview at CSDN to make progress and come on.

Topics: Javascript Front-end React jsx