Frontend - React
#
Prerequisites- Deployed Todos API
- NodeJS
#
SetupIn 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 start
and go to localhost:3000
#
Setting Up React RouterLet'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 Stylingimport "milligram";// Import the BrowserRouter Component and Rename it Routerimport { 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
components
folder for holding small pieces of UI and apages
folder for components that act routes/pages.
#
Create the following Componentssrc/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 ComponentsSo 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 Componentsimport AllPosts from "./pages/AllPosts";import SinglePost from "./pages/SinglePost";import Form from "./pages/Form";
// Import React and hooksimport React, { useState, useEffect } from "react";
// Import components from React Routerimport { 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 TodosWe 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 APIconst getTodos = async () => { const response = await fetch(url); const data = await response.json(); setPosts(data);};
//////////////// useEffects//////////////
// useEffect to get list of todos when page loadsuseEffect(() => { getTodos();}, []);
#
Rendering Our TodosWe 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 propsconst 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 ComponentOur 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 matchconst 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 FormOne 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 hookimport React, { useState } from "react";
//destructure out props, including router prop historyconst 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 TodoNow 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 APIconst getTodos = async () => { const response = await fetch(url); const data = await response.json(); setPosts(data);};
// Function to add todo from form dataconst 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 Urlconst url = "https://api.herokuapp.com/todos/";
// State to Hold The List of Postsconst [posts, setPosts] = useState([]);
// an object that represents a null todoconst 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 Routerimport { 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 TodosTo 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 Urlconst url = "https://api.herokuapp.com/todos/";
// State to Hold The List of Postsconst [posts, setPosts] = useState([]);
// an object that represents a null todoconst 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 APIconst getTodos = async () => { const response = await fetch(url); const data = await response.json(); setPosts(data);};
// Function to add todo from form dataconst 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 editconst getTargetTodo = (todo) => { setTargetTodo(todo); props.history.push("/edit");};
// Function to edit todo on form submissionconst 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 matchconst 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 TodoThe 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 APIconst getTodos = async () => { const response = await fetch(url); const data = await response.json(); setPosts(data);};
// Function to add todo from form dataconst 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 editconst getTargetTodo = (todo) => { setTargetTodo(todo); props.history.push("/edit");};
// Function to edit todo on form submissionconst 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 submissionconst 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 matchconst 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;