JS 10 - React Crash Course, Todo List

By Sheldon L Published at 2020-03-13 Updated at 2020-03-13


Get started

npx create-react-app my-app
cd my-app
code .
npm start

npm i uuid
npm i react-router-dom
npm i axios         # http req
import React from 'react';
import ReactDOM from 'react-dom';

import App from './App';


ReactDOM.render(<App />, document.getElementById('root'));

TodoList App

import React, { Component } from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import axios from 'axios';

import Header from './components/layout/Header';
import About from './components/pages/About';

import Todos from './components/Todos';
import AddTodo from './components/AddTodo';

import './App.css';

const uuid=require('uuid');


class App extends Component {
  state = {
    todos: []
  };

  componentDidMount() {
    axios.get('https://jsonplaceholder.typicode.com/todos?_limit=10')
      .then(res => this.setState({ todos: res.data }))
      .catch(err => console.log(err))
  }

  // Toggol markComplete
  markComplete = (id) => {
    this.setState({ todos: this.state.todos.map(todo => {
      if (todo.id === id) {
        todo.completed = !todo.completed
      }
      return todo;
    }) })
  }

  // Toggle delTodo
  delTodo = (id) => {
    axios.delete(`https://jsonplaceholder.typicode.com/todos/${id}`)
      .then(this.setState({todos: [...this.state.todos.filter(todo => todo.id !== id)] }))
  }

  // Toggl addTodo
  addTodo = (title) => {
    axios.post('https://jsonplaceholder.typicode.com/todos',
      {
        id: uuid.v4(),
        title: title,
        completed: false,
      }
    ).then(res => this.setState({ todos: [...this.state.todos, res.data] }))
     .catch(err => console.log(err))
  }

  render() {
    return (
      <Router>
        <div className="App">
          <div className="container">
            <Header />
            <Route exact path="/" render={props => (
              <React.Fragment>

                <AddTodo
                  addTodo={this.addTodo}
                />
                <Todos todos={this.state.todos}
                  markComplete={this.markComplete}
                  delTodo={this.delTodo}
                />

              </React.Fragment>
              )}
            />
            <Route exact path="/about" component={About} />
          </div>
        </div>
      </Router>
    );
  };
}

export default App;
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: Arial, Helvetica, sans-serif;
  line-height: 1.4;
}

a {
  color: #333;
  text-decoration: none;
}

.container {
  padding: 0 0;
}

.btn {
  display: inline-block;
  border: none;
  background: #555;
  color: #fff;
  padding: 7px 20px;
  cursor: pointer;
}

.btn:hover {
  background: #666;
}
import React from 'react'
import { Link } from 'react-router-dom';

function Header() {
  return (           // in function return = return + render
    <div>
      <header style={headerStyle}>
        <h1>TodoList</h1>
        <Link style= to="/">Home</Link>{' | '}
        <Link style= to="/about">About</Link>
      </header>
    </div>
  )
}

const headerStyle = {
  background: '#333',
  color: '#fff',
  textAlign: 'center',
  padding: '10px',
}

export default Header;
import React from 'react'

function About() {
  return (
    <React.Fragment>
      <h1>About</h1>
      <p>This is the TodoList v1.0.0</p>
    </React.Fragment>
  )
}

export default About
import React, { Component } from "react";
import TodoItem from "./TodoItem";
import PropTypes from 'prop-types';

class Todos extends Component {

  render() {
    return this.props.todos.map((todo) => (
      <TodoItem
        key={todo.id}
        todo={todo}
        markComplete={this.props.markComplete}
        delTodo={this.props.delTodo}
      />
    ))
  }
}

// PropTypes
Todos.propTypes = {
  todos: PropTypes.array.isRequired,
  markComplete: PropTypes.func.isRequired,
  delTodo:PropTypes.func.isRequired,
}

export default Todos;
import React, { Component } from 'react';
import PropTypes from 'prop-types';

export class TodoItem extends Component {
  getCompleteStyle = () => {
    return {
      background: '#f4f4f4',
      padding: '10px',
      borderBottom: '1px #ccc dotted',
      textDecoration: this.props.todo.completed ? 'line-through' : 'none'
    }
  }

  render() {
    const { id, title } = this.props.todo
    return (
      <div style={this.getCompleteStyle()}>
        <p>
          <input type="checkbox"
            onChange={this.props.markComplete.bind(this, id)}
            defaultChecked={this.props.todo.completed ? true : false}
          />
          {' '} { title }
          <button onClick={this.props.delTodo.bind(this, id)} style={btnStyle}>x</button>
        </p>
      </div>
    )
  }
}

// PropTypes
TodoItem.propTypes = {
  todo: PropTypes.object.isRequired,
  markComplete: PropTypes.func.isRequired,
  delTodo:PropTypes.func.isRequired,
}

const btnStyle = {
  background: '#ff0000',
  color: '#fff',
  border: 'none',
  padding: '1px 5px',
  borderRadius: '50%',
  cursor: 'pointer',
  float: 'right'
}

export default TodoItem
import React, { Component } from 'react'
import PropTypes from 'prop-types'

export class AddTodo extends Component {

  state = {
    title: ''
  }

  onSubmit = (e) => {
    e.preventDefault();
    this.props.addTodo(this.state.title);
    this.setState({ title: '' })
  }

  onChange = (e) => this.setState({
    [e.target.name]: e.target.value   // name="title" in <input>
  });

  render() {
    return (
      <form onSubmit={this.onSubmit} style=>
        <input
          style=
          type="text"
          name="title"
          placeholder="Add todo..."
          value={this.state.title}
          onChange={this.onChange}
        />
        <input
          style=
          type="submit"
          value="Submit"
          className="btn"
        />
      </form>
    )
  }
}

// PropTypes
AddTodo.propTypes = {
  addTodo: PropTypes.func.isRequired,
}

export default AddTodo