Frontend - React
Prerequisites
- Deployed Todos API
- NodeJS
Setup
- 
In your terminal spin-up a new React Project npx create-react-app@latest todofront
- 
Install support Libraries to be used npm install react-router-dom milligram
- 
test out dev server npm startand go to localhost:3000
Setting Up React Router
Let's import the Router components and wrap our App components like this in index.js.
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
// Import Milligram for Some Default Styling
import "milligram";
// Import the BrowserRouter Component and Rename it Router
import { BrowserRouter as Router, Route } from "react-router-dom";
import reportWebVitals from "./reportWebVitals";
ReactDOM.render(
  // Wrap our App Component inside router so App and children can use router
  // Pass the app component into Route to give it access to router props
  <Router>
    <React.StrictMode>
      <Route path="/" component={App} />
    </React.StrictMode>
  </Router>,
  document.getElementById("root")
);
Setting Up Our Files
- In src create a componentsfolder for holding small pieces of UI and apagesfolder for components that act routes/pages.
Create the following Components
src/components/post.js
import React from "react";
const Post = (props) => {
  return <h1>Post</h1>;
};
export default Post;
src/pages/AllPosts.js
import React from "react";
const AllPosts = (props) => {
  return <h1>AllPosts</h1>;
};
export default AllPosts;
src/pages/SinglePost.js
import React from "react";
const SinglePost = (props) => {
  return <h1>Post</h1>;
};
export default SinglePost;
src/pages/Form.js
import React from "react";
const Form = (props) => {
  return <h1>Post</h1>;
};
export default Form;
Importing Our Components
So now it's time to bring our components into our App.js where we will setup four client-side routes.
- 
"/" -> which will Render all of our todos in the AllPosts component 
- 
"/post/:id" -> which will render an individual todo in our SinglePost component 
- 
"/new" -> which render our Form component for creating a new Todo 
- 
"/edit" -> which renders our Form to edit 
/src/App.js
// Import All Our Components
import AllPosts from "./pages/AllPosts";
import SinglePost from "./pages/SinglePost";
import Form from "./pages/Form";
// Import React and hooks
import React, { useState, useEffect } from "react";
// Import components from React Router
import { Route, Switch } from "react-router-dom";
function App(props) {
  ////////////////////
  // Style Objects
  ////////////////////
  const h1 = {
    textAlign: "center",
    margin: "10px",
  };
  ///////////////
  // State & Other Variables
  ///////////////
  // Our Api Url
  const url = "https://api.herokuapp.com/todos/";
  // State to Hold The List of Posts
  const [posts, setPosts] = useState([]);
  //////////////
  // Functions
  //////////////
  //////////////
  // useEffects
  //////////////
  /////////////////////
  // returned JSX
  /////////////////////
  return (
    <div>
      <h1 style={h1}>My Todo List</h1>
      <Switch>
        <Route
          exact
          path="/"
          render={(routerProps) => <AllPosts {...routerProps} posts={posts} />}
        />
        <Route
          path="/post/:id"
          render={(routerProps) => (
            <SinglePost {...routerProps} posts={posts} />
          )}
        />
        <Route
          path="/new"
          render={(routerProps) => <Form {...routerProps} />}
        />
        <Route
          path="/edit"
          render={(routerProps) => <Form {...routerProps} />}
        />
      </Switch>
    </div>
  );
}
export default App;
Getting Our Todos
We will create a getTodos function that will get all of our todos from our deployed API then call it inside a useEffect.
src/App.js
//////////////
// Functions
//////////////
// Function to get list of Todos from API
const getTodos = async () => {
  const response = await fetch(url);
  const data = await response.json();
  setPosts(data);
};
//////////////
// useEffects
//////////////
// useEffect to get list of todos when page loads
useEffect(() => {
  getTodos();
}, []);
Rendering Our Todos
We are already passing the posts as props to AllPosts so now let's go to src/pages/AllPosts.js and render those todos!!!
src/AllPosts.js
import React from "react";
import Post from "../components/post";
const AllPosts = (props) => {
  // For each post in the array render a Post component
  return props.posts.map((post) => <Post post={post} key={post.id} />);
};
export default AllPosts;
Let's define how an individual post will look like in src/components/post.js
import React from "react";
import { Link } from "react-router-dom";
//destructure the post from props
const Post = ({ post }) => {
  //////////////////
  // Style Objects
  //////////////////
  const div = {
    textAlign: "center",
    border: "3px solid",
    margin: "10px auto",
    width: "80%",
  };
  return (
    <div style={div}>
      <Link to={`/post/${post.id}`}>
        <h1>{post.subject}</h1>
      </Link>
      <h2>{post.details}</h2>
    </div>
  );
};
export default Post;
SinglePost Component
Our component to see an individual post, src/pages/SinglePost.js
import React from "react";
import { Link } from "react-router-dom";
// destructuring the props needed to get our post, including router prop match
const SinglePost = ({ posts, match }) => {
  const id = parseInt(match.params.id); //get the id from url param
  const post = posts.find((post) => post.id === id);
  ////////////////////
  // Styles
  ///////////////////
  const div = {
    textAlign: "center",
    border: "3px solid green",
    width: "80%",
    margin: "30px auto",
  };
  return (
    <div style={div}>
      <h1>{post.subject}</h1>
      <h2>{post.details}</h2>
      <Link to="/">
        <button>Go Back</button>
      </Link>
    </div>
  );
};
export default SinglePost;
Cool, we can now see our todos!
Setting Up Our Form
One Benefit of React is Reusability. Let's design a form component we can reuse for our New and Edit routes. Our form will need three props to make it useable it multiple use cases.
- 
initialTodo: This should be an object to initialize the forms state 
- 
handleSubmit: This will be the function that is run when the form is submitted 
- 
buttonLabel: This will be the label for the submit button on the form 
src/pages/form.js
// Import useState hook
import React, { useState } from "react";
//destructure out props, including router prop history
const Form = ({ initialTodo, handleSubmit, buttonLabel, history }) => {
  ////////////////
  // The Form Data State
  ////////////////
  // Initiallize the form with the initialTodo state
  const [formData, setFormData] = useState(initialTodo);
  //////////////////////////
  // Functions
  //////////////////////////
  // Standard React Form HandleChange Function
  const handleChange = (event) => {
    setFormData({ ...formData, [event.target.name]: event.target.value });
  };
  // Function to run when form is submitted
  const handleSubmisson = (event) => {
    //prevent form refresh
    event.preventDefault();
    //pass formData to handleSubmit prop function
    handleSubmit(formData);
    //push user back to main page
    history.push("/");
  };
  // Our Form, an input for the subject and details fields and a submit button
  return (
    <form onSubmit={handleSubmisson}>
      <input
        type="text"
        onChange={handleChange}
        value={formData.subject}
        name="subject"
      />
      <input
        type="text"
        onChange={handleChange}
        value={formData.details}
        name="details"
      />
      <input type="submit" value={buttonLabel} />
    </form>
  );
};
export default Form;
Creating a Todo
Now that we have our form ready to go let's go back to App and create and pass the necessary props and add a button to get to the form.
- add function that creates a todo
//////////////
// Functions
//////////////
// Function to get list of Todos from API
const getTodos = async () => {
  const response = await fetch(url);
  const data = await response.json();
  setPosts(data);
};
// Function to add todo from form data
const addTodos = async (newTodo) => {
  const response = await fetch(url, {
    method: "post",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(newTodo),
  });
  // get updated list of todos
  getTodos();
};
- a null todo object
///////////////
// State & Other Variables
///////////////
// Our Api Url
const url = "https://api.herokuapp.com/todos/";
// State to Hold The List of Posts
const [posts, setPosts] = useState([]);
// an object that represents a null todo
const nullTodo = {
  subject: "",
  details: "",
};
- update the props on the "/new" route
<Route
  path="/new"
  render={(routerProps) => (
    <Form
      {...routerProps}
      initialTodo={nullTodo}
      handleSubmit={addTodos}
      buttonLabel="create todo"
    />
  )}
/>
- import Link
// Import components from React Router
import { Route, Switch, Link } from "react-router-dom";
- create a "create new todo" button with styling!
////////////////////
// Style Objects
////////////////////
const h1 = {
  textAlign: "center",
  margin: "10px",
};
const button = {
  backgroundColor: "navy",
  display: "block",
  margin: "auto",
};
      <h1 style={h1}>My Todo List</h1>
      <Link to="/new"><button style={button}>Create New Todo</button></Link>
Editing Todos
To edit the todo we need to know which todo the user is editing, we will create new state to track this todo and create a function to change it that we'll pass down to the SinglePost component.
src/App.js
- create the state for the targeted todo
///////////////
// State & Other Variables
///////////////
// Our Api Url
const url = "https://api.herokuapp.com/todos/";
// State to Hold The List of Posts
const [posts, setPosts] = useState([]);
// an object that represents a null todo
const nullTodo = {
  subject: "",
  details: "",
};
// const state to hold todo to edit
const [targetTodo, setTargetTodo] = useState(nullTodo);
- create the function to select the todo and head to edit form and another function for when the form is submitted.
//////////////
// Functions
//////////////
// Function to get list of Todos from API
const getTodos = async () => {
  const response = await fetch(url);
  const data = await response.json();
  setPosts(data);
};
// Function to add todo from form data
const addTodos = async (newTodo) => {
  const response = await fetch(url, {
    method: "post",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(newTodo),
  });
  // get updated list of todos
  getTodos();
};
// Function to select todo to edit
const getTargetTodo = (todo) => {
  setTargetTodo(todo);
  props.history.push("/edit");
};
// Function to edit todo on form submission
const updateTodo = async (todo) => {
  const response = await fetch(url + todo.id + "/", {
    method: "put",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(todo),
  });
  // get updated list of todos
  getTodos();
};
- update the props of the "/post/:id" and "edit" routes
<Switch>
  <Route
    exact
    path="/"
    render={(routerProps) => <AllPosts {...routerProps} posts={posts} />}
  />
  <Route
    path="/post/:id"
    render={(routerProps) => (
      <SinglePost {...routerProps} posts={posts} edit={getTargetTodo} />
    )}
  />
  <Route
    path="/new"
    render={(routerProps) => (
      <Form
        {...routerProps}
        initialTodo={nullTodo}
        handleSubmit={addTodos}
        buttonLabel="create todo"
      />
    )}
  />
  <Route
    path="/edit"
    render={(routerProps) => (
      <Form
        {...routerProps}
        initialTodo={targetTodo}
        handleSubmit={updateTodo}
        buttonLabel="update todo"
      />
    )}
  />
</Switch>
- Add an edit button when looking at an individual todo
src/pages/SinglePost.js
import React from "react";
import { Link } from "react-router-dom";
// destructuring the props needed to get our post, including router prop match
const SinglePost = ({ posts, match, edit }) => {
  const id = parseInt(match.params.id); //get the id from url param
  const post = posts.find((post) => post.id === id);
  ////////////////////
  // Styles
  ///////////////////
  const div = {
    textAlign: "center",
    border: "3px solid green",
    width: "80%",
    margin: "30px auto",
  };
  return (
    <div style={div}>
      <h1>{post.subject}</h1>
      <h2>{post.details}</h2>
      <button onClick={(event) => edit(post)}>Edit</button>
      <Link to="/">
        <button>Go Back</button>
      </Link>
    </div>
  );
};
export default SinglePost;
Deleting a Todo
The final piece is deleting a todo, all we have to do is create a delete function in app, send it down to SinglePost and make a button, tada!
- make another function in App.js
//////////////
// Functions
//////////////
// Function to get list of Todos from API
const getTodos = async () => {
  const response = await fetch(url);
  const data = await response.json();
  setPosts(data);
};
// Function to add todo from form data
const addTodos = async (newTodo) => {
  const response = await fetch(url, {
    method: "post",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(newTodo),
  });
  // get updated list of todos
  getTodos();
};
// Function to select todo to edit
const getTargetTodo = (todo) => {
  setTargetTodo(todo);
  props.history.push("/edit");
};
// Function to edit todo on form submission
const updateTodo = async (todo) => {
  const response = await fetch(url + todo.id + "/", {
    method: "put",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(todo),
  });
  // get updated list of todos
  getTodos();
};
// Function to edit todo on form submission
const deleteTodo = async (todo) => {
  const response = await fetch(url + todo.id + "/", {
    method: "delete",
  });
  // get updated list of todos
  getTodos();
  props.history.push("/");
};
- pass the function as a prop to SinglePost
<Route
  path="/post/:id"
  render={(routerProps) => (
    <SinglePost
      {...routerProps}
      posts={posts}
      edit={getTargetTodo}
      deleteTodo={deleteTodo}
    />
  )}
/>
- Add the button in singlepost
import React from "react";
import { Link } from "react-router-dom";
// destructuring the props needed to get our post, including router prop match
const SinglePost = ({ posts, match, edit, deleteTodo }) => {
  const id = parseInt(match.params.id); //get the id from url param
  const post = posts.find((post) => post.id === id);
  ////////////////////
  // Styles
  ///////////////////
  const div = {
    textAlign: "center",
    border: "3px solid green",
    width: "80%",
    margin: "30px auto",
  };
  return (
    <div style={div}>
      <h1>{post.subject}</h1>
      <h2>{post.details}</h2>
      <button onClick={(event) => edit(post)}>Edit</button>
      <button onClick={(event) => deleteTodo(post)}>Delete</button>
      <Link to="/">
        <button>Go Back</button>
      </Link>
    </div>
  );
};
export default SinglePost;