React, the entry program for Redux – todos

Time:2021-12-30

I found that the framework entry programs I saw recently have becomeTodos, it took me three hours to achieve one myselfTodos… Sigh that the front-end entry is becoming more and more complex. I miss it a few years agohello worldThe age of…

I make complaints about it.ReactandReduxHere,cssThe style is completely fromhereCopy it.

The structure of the code is prepared as follows:
React, the entry program for Redux - todos

It is transformed into code structure, which is like this:
React, the entry program for Redux - todos

In addition, according to the official example, inHeadersinistertoggleAllThe button is onSectionYes.

Redux

Types/todos.js

stayReduxIn,typeIs used foractionandreducerA time of communicationflag, letreducerKnow what this request is.

I’m used to puttingtypeSeparate them separately and list them in a fileReduxYour files are cleaner and easier to manage.

/** ------------------------- TODO ---------------------*/
export const TODO_INSERT_ITEM = 'TODO_INSERT_ITEM';
export const TODO_DELETE_ITEM = 'TODO_DELETE_ITEM';
export const TODO_SWITCH_FILTER = 'TODO_SWITCH_FILTER';
export const TODO_TOGGLE_ACTIVE = 'TODO_TOGGLE_ACTIVE';
export const TODO_TOGGLE_ALL = 'TODO_TOGGLE_ALL';
export const TODO_CHANGE_VALUE = 'TODO_CHANGE_VALUE';
export const TODO_CLEAR_COMPLETED = 'TODO_CLEAR_COMPLETED';

Actions/todos.js

According to the above listedtype, list the correspondingaction creator

import { TODO_INSERT_ITEM, TODO_DELETE_ITEM, TODO_SWITCH_FILTER, TODO_TOGGLE_ACTIVE, TODO_TOGGLE_ALL, TODO_CHANGE_VALUE, TODO_CLEAR_COMPLETED } from '../types';

//Insert a todo 
export function insertItem(value){
  return {
    type: TODO_INSERT_ITEM,
    value
  };
}

//Delete a todo
export function deleteItem(id) {
  return {
    type: TODO_DELETE_ITEM,
    id
  }
}

//Transition the state of a todo
export function switchFilter(filter) {
  return {
    type: TODO_SWITCH_FILTER,
    filter
  }
}

//Clear all completed todo
export function clearCompleted(){
  return {
    type: TODO_CLEAR_COMPLETED
  }
}

export function toggleActive(id){
  return {
    type: TODO_TOGGLE_ACTIVE,
    id
  }
}

//Transition all States to active
export function toggleAll(active){
  return {
    type: TODO_TOGGLE_ALL,
    active
  }  
}

//Change the corresponding todo value
export function changeValue(id, value) {
  return {
    type: TODO_CHANGE_VALUE,
    id,
    value
  }
}

Reducers/todos.js

stayreducerSeveral points to be noted in:

  1. InitializedstateWant fromlocalStorageGet in

  2. Every time you make a change, you have to update it againlocalStorage

  3. When the data has not changed, try to use the original data to reducere-render

  4. For ease of searching, I use it herelodashofuniqueIdMethod, give eachitemAdd oneid

  5. For ease of storage and display, I include one hereitemsUsed to save allitems, oneshowedItemsUsed to store information to be displayeditems

First provide a simple abbreviationlocalStoragemethod

const local = (function(KEY){
  return {
    set: value=>{ localStorage.setItem(KEY, value) },
    get: ()=>localStorage.getItem(KEY),
    check: ()=>localStorage.getItem(KEY) != undefined
  };
})("todo");

Then several auxiliary methods:

//Create a new item
function generateItem(value) {
  return {
    id: _.uniqueId(),
    active: true,
    value
  }
}

//Judge whether the current item is being displayed
function include(active, filter) {
  return filter === "ALL" || (active && filter === "ACTIVE")  || (!active && filter === "COMPLETED");
}

//Get the items to be displayed on the page
function getShowedItems(items, filter) {
  let showedItems = [], keys = Object.keys(items);
  for(let i = 0; i < keys.length; i++){
    let item = items[keys[i]];

    if(include(item.active, filter)) {
      showedItems.push(item);
    }
  }
  return showedItems;
}

When initializing, getlocalStorageOr give a default value:

let defaultTodo;
(function(){
  if(local.check()) {
    defaultTodo = JSON.parse(local.get());
  } else {
    defaultTodo = {
      items: {},
      filter: "ALL", // ALL, COMPLETED, ACTIVE
      count: 0,
      showedItems: [],
      hasCompleted: false
    }
  }
})();

Note: I don’t like the way that all processing methods are put in one function in the document, so I wrote a method to separate reducers into multiple functions

//It's very simple. It's actually a circular call...
export function combine(reducers){
  return (state, action) => {
    for(let key in reducers) {
      if(reducers.hasOwnProperty(key)) {
          state = reducers[key](state, action) || state;
      }
    }
    return state;
  }
}

All belowreducers, I won’t say much about the specific logic:

let exports = {};
exports.insertItem = function(state = defaultTodo, action) {
  const type = action.type;

  if(type === TODO_INSERT_ITEM) {
    let { count, items, filter, showedItems } = state;
    let item = generateItem(action.value);

    items = {
      ...items,
      [item.id] : item 
    }

    count = count + 1;
    
    state = {
      ...state,
      items,
      count,
      showedItems: filter !== "COMPLETED" ? getShowedItems(items, filter) : showedItems
    }

    local.set(JSON.stringify(state));
  }

  return state;
}

exports.deleteItem = function(state = defaultTodo, action) {
  const type = action.type;

  if(type === TODO_DELETE_ITEM && state.items[action.id]) {
    let { count, items, filter, hasCompleted } = state;
    let item = items[action.id];

    delete items[action.id];
    if(item.active) count--;
    
    state = {
      ...state,
      items,
      count,
      showedItems: include(item.active, filter) ? getShowedItems(items, filter) : state.showedItems,
      hasCompleted: Object.keys(items).length !== count
    }

    local.set(JSON.stringify(state));
  }

  return state;
}

exports.switchFilter = function(state = defaultTodo, action) {
  const type = action.type;
  if(type === TODO_SWITCH_FILTER && state.filter !== action.filter) {
    state = {
      ...state,
      filter: action.filter,
      showedItems: getShowedItems(state.items, action.filter)
    }

    local.set(JSON.stringify(state));
  }

  return state;
}

exports.clearCompleted = function(state = defaultTodo, action) {
  const type = action.type;
  if(type === TODO_CLEAR_COMPLETED) {
    let { items, filter, showedItems } = state;

    let keys = Object.keys(items);
    let tempItems = {};
    for(let i = 0; i < keys.length; i++) {
      let item = items[keys[i]];
      if(item.active) {
        tempItems[item.id] = item;
      }
    }

    state = {
      ...state,
      items: tempItems,
      showedItems: filter === "ACTIVE" ? showedItems : getShowedItems(tempItems, filter),
      hasCompleted: false
    }
    local.set(JSON.stringify(state));
  }

  return state;
}

exports.toggleActive = function(state = defaultTodo, action) {
  const { type, id } = action;

  if(type === TODO_TOGGLE_ACTIVE && state.items[id]) {
    let { items, filter, count, showedItems } = state;
    
    let item = items[id];
    item.active = !item.active;
    
    items = {
      ...items,
      [id]: item
    };

    if(item.active) count++; //  If it becomes active
    else count--; //  If it becomes completed

    state = {
      ...state,
      items,
      count,
      showedItems: getShowedItems(items, filter),
      hasCompleted: Object.keys(items).length !== count
    }

    local.set(JSON.stringify(state));
  }
  return state;
}

exports.toggleAll = function(state = defaultTodo, action) {
  const { type, active } = action;

  if(type === TODO_TOGGLE_ALL) {
    let { items, filter, showedItems } = state;
    let keys = Object.keys(items);
    
    for(let i = 0; i < keys.length; i++) {
      items[keys[i]].active = active;
    }

    let count = active ? keys.length : 0; 

    state = {
      ...state,
      items,
      count,
      showedItems: include(active, filter) ? getShowedItems(items, filter) : showedItems,
      hasCompleted: !active
    }

    local.set(JSON.stringify(state));
  }
  return state;
}

exports.changeValue = function(state = defaultTodo, action){
  const { type, id } = action;

  if(type === TODO_CHANGE_VALUE && state.items[id]) {
    let { items, filter, showedItems } = state;
    let item = items[id];

    item.value = action.value;

    items = {
      ...items,
      [id]: item    
    };

    state = {
      ...state,
      items,
      showedItems: include(item.active, filter) ? getShowedItems(items, filter) : showedItems
    }
    local.set(JSON.stringify(state));
  }
  return state;
}

export default combine(exports); //  Package with combine method

ReducersI’m in a lot ofshowedItemsCheck whether there is any change. If there is no change, use the original one for convenience inSectionComponent, you can avoid unnecessary re rendering.Although, it doesn’t seem to be of any use to me. However, it is necessary for complex projects.

Views/Todos.js

import React from 'react';
import Header from 'containers/ToDo/Header';
import Footer from 'containers/ToDo/Footer';
import Section from 'containers/ToDo/Section';
import 'components/ToDo/index.scss';

export default class ToDo extends React.Component {
  constructor(props) {
    super(props);
  }

  render(){
    return (
      <div id="TODO">
        <Header />
        <Section />
        <Footer />
      </div>
    )
  }
}

Contianers

Header.js

Header.jsMainly responsible forlogoRendering, and thatinputBox.

utilizecontrolled componentyesinputThen monitor the keyboard to determine whether to enter or submit.

import React from 'react';
import { CONTROLS } from 'utils/KEYCODE';
import { connect } from 'react-redux';
import { insertItem } from 'actions/todo';

class Header extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      value: ""
    };
  }

  onChange = (e)=>{
    let value = e.target.value;
    this.setState({
      value
    });
  }

  onKeyDown = (e)=>{
    let keyCode = e.keyCode;

    if(keyCode === CONTROLS.ENTER && this.state.value !== "") {
      this.props.insertItem(this.state.value);
      this.setState({
        value: ""
      });

      e.preventDefault();
      e.stopPropagation();
    }
  }

  render(){
    return (
      <header className="todo-header">
        <h1>todos</h1>
        <input type="text" className="insert" value={this.state.value} onChange={this.onChange} onKeyDown={this.onKeyDown} placeholder="What needs to be done?" />
      </header>
    )
  }
}

export default connect(null, { insertItem })(Header);

Footer.js

FooterMainly for displayquantityfilter, Clear completed button

import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { switchFilter, clearCompleted } from 'actions/todo';

class Footer extends React.Component {
  constructor(props) {
    super(props);
  }

  switchFilter = (filter)=>{
    this.props.switchFilter(filter.toUpperCase());
  }

  render(){
    const { count, hasCompleted, filter, clearCompleted } = this.props;

    if(count === 0 && !hasCompleted) return null;

    return (
      <footer className="todo-footer">
        <span className="counter">{count} items left</span>
        <ul className="filter">
          {
            ["All", "Active", "Completed"].map((status, index)=>{
              return (
                <li key={status} className={status.toUpperCase() === filter ? "active" : ""}>
                  <a href="javascript:;" onClick={()=>{ this.switchFilter(status) }}>{status}</a>
                </li>
              );
            })
          }
        </ul>
        {
          hasCompleted && <button className="clear-completed" onClick={clearCompleted}>Clear completed</button>
        }
      </footer>
    )
  }
}

Footer.propTypes = {
  count: PropTypes.number.isRequired,
  hasCompleted: PropTypes.bool.isRequired,
  filter: PropTypes.oneOf(['ALL', 'ACTIVE', 'COMPLETED']).isRequired
}

function mapStateToProps(state){
  let {
    todo: {
      count,
      hasCompleted,
      filter
    }
  } = state;

  return {
    count,
    hasCompleted,
    filter
  }
}

export default connect(mapStateToProps, { switchFilter, clearCompleted })(Footer);

Section.js

SectionContains a list of todos, anddelete, Change stateModify value, toggle allAnd other functions.

import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { deleteItem, toggleActive, toggleAll, changeValue } from 'actions/todo';
import Item from 'components/ToDo/Item';

class Section extends React.Component {
  constructor(props) {
    super(props);
  }

  render(){
    const { showedItems=[], count, toggleAll, changeValue, deleteItem, toggleActive, hasCompleted } = this.props;

    return (
      <section className="todo-section">
        <input type="checkbox" className="toggle-all" onChange={()=>{ toggleAll(count === 0) }} checked={count === 0 && hasCompleted} />
        <ul className="todo-items">
          {
            showedItems.map(item=><Item key={item.id} {...item} onValueChange={changeValue} onItemDelete={deleteItem} toggleActive={toggleActive} />)
          }
        </ul>
      </section>
    )
  }
}

Section.propTypes = {
  showedItems: PropTypes.arrayOf(PropTypes.object).isRequired,
  count: PropTypes.number.isRequired
}

function mapStateToProps(state) {
  let {
    todo: {
      showedItems,
      count,
      hasCompleted
    }
  } = state;

  return {
    showedItems,
    count,
    hasCompleted
  };
}

export default connect(mapStateToProps, { deleteItem, toggleActive, toggleAll, changeValue })(Section);

Components

Item.js

import React from 'react';
import ReactDOM from 'react-dom';

export default class Item extends React.Component {
  constructor(props) {
    super(props);

    this.state = { value: props.value, editing: false };
  }

  componentDidUpdate() {
    if(this.state.editing) {
      var node = ReactDOM.findDOMNode(this.edit);
      node.focus();
    }
  }

  inputInstance = (input) => {
    this.edit = input;
  }

  onToggle = (e)=>{
    this.props.toggleActive(this.props.id, !e.target.checked);
  }

  onValueChange = (e)=>{
    this.props.onValueChange(this.props.id, e.target.value);
    this.setState({
      editing: false
    });
  }

  onEditChange = (e)=>{
    this.setState({
      value: e.target.value
    });
  }

  onDoubleClick = (e)=>{
    this.setState({
      editing: true
    });
  }

  render(){
    let { id, active, onItemDelete, onValueChange } = this.props;
    let { value, editing } = this.state;

    return (
      <li className={editing ? "editing" : ""}>
        { editing || (<div className="view">
          <input type="checkbox" className="toggle" onChange={this.onToggle} checked={!active} />
          <label className={ `item-value${active ? "" : " completed"}`} onDoubleClick={this.onDoubleClick} >{value}</label>
          <button className="delete" onClick={()=>{ onItemDelete(id) }}></button>
        </div>)
        }
        { editing && <input type="text" ref={this.inputInstance} className="edit" onBlur={this.onValueChange} onChange={this.onEditChange} value={value} /> }
      </li>
    )
  }
}

When writing components, I think it’s good to post the code and have a look. Not much to explain…