Categories
frontend javascript reactjs

Create your own Exercise Planner using React.js [Complete Code]

Hey everyone πŸ‘‹πŸ»,

In this article, let us see how we can build an Exercise Planner Application using React.js.

✏ Demo for the Project & Initial Setup

So the first thing you need to do is to create a new project.
Make sure to install Node.js onto your system because we will be needing that for our project.

We will be using this tool for creating a new React Project :

Create React App :

image

Install Node.js :image

Create a new React Project

// To create a new React Project 
npx create-react-app <project_name>
cd <project_name>
// To run the project 
npm start

Install React Router DOM package

npm install react-router-dom 

Install JSON-server package globally

We will be using JSON-server for our project.

npm install json-server -g

Create a new file – exercises.json

This is the file that will contain all the exercises that we will be needing in this application. This file will serve as a local JSON store for our application.

store/data.json

{
  "exercises": [
    {
      "title": "Pushups",
      "details": "Pushups are beneficial for building upper body strength. They work the triceps, pectoral muscles, and shoulders. When done with proper form, they can also strengthen the lower back and core by engaging (pulling in) the abdominal muscles",
      "complete": false,
      "id": 119
    }
  ]
}

Inside a new terminal, run the json-server by running the command

json-server -g ./src/store/data.json --watch --port=3111

Complete Code πŸ‘¨πŸ»β€πŸ’»

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';

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

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

App.jsx

import {Switch, Route } from "react-router-dom"
import HomePage from './pages/HomePage';
import './App.css';
import CreateExercise from "./pages/CreateExercise";
import Navbar from "./components/Navbar";
import EditExercise from "./pages/EditExercise";

function App() {
  return (
    <div className="App">
      <Navbar />
      <Switch>
        <Route path="/home" exact>
          <HomePage />
        </Route>
        <Route path="/create-exercise" exact>
          <CreateExercise />
        </Route>
        <Route path="/exercises/:id/edit">
          <EditExercise />
        </Route>
      </Switch>
    </div>
  );
}

export default App;


App.css

.App {
  text-align: center;
}

index.css

html {
  --pink: #D60087;
  --golden: goldenrod;
  --green: rgba(216, 235, 48, 0.83);
  --text-shadow: 2px 2px 0 rgba(0,0,0,0.2);
  font-size: 62.5%;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
  box-sizing: border-box;
}

*, *:before, *:after {
  box-sizing: inherit;
}

body {
  font-size: 2rem;
  line-height: 1.5;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='1440' height='560' preserveAspectRatio='none' viewBox='0 0 1440 560'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1000%26quot%3b)' fill='none'%3e%3crect width='1440' height='560' x='0' y='0' fill='%230e2a47'%3e%3c/rect%3e%3cpath d='M136.72 348.61a22.94 22.94 0 1 0 43.22-15.38z' stroke='%23e73635'%3e%3c/path%3e%3cpath d='M511.55 165.45L536.61 165.45L536.61 190.51L511.55 190.51z' stroke='%23037b0b'%3e%3c/path%3e%3cpath d='M483.33 455.66 a20.42 20.42 0 1 0 40.84 0 a20.42 20.42 0 1 0 -40.84 0z' fill='%23e73635'%3e%3c/path%3e%3cpath d='M1228-20.28a48.24 48.24 0 1 0-63.63 72.52z' stroke='%23037b0b'%3e%3c/path%3e%3cpath d='M1392.69 128.01 a29.96 29.96 0 1 0 59.92 0 a29.96 29.96 0 1 0 -59.92 0z' stroke='%23d3b714'%3e%3c/path%3e%3cpath d='M1076.84 554.49L1113.49 554.49L1113.49 591.14L1076.84 591.14z' stroke='%23d3b714'%3e%3c/path%3e%3cpath d='M756.26 7.46L795.56 7.46L795.56 35.82L756.26 35.82z' fill='%23d3b714'%3e%3c/path%3e%3cpath d='M25.99 370.04L26.54 370.04L26.54 423.32L25.99 423.32z' fill='%23d3b714'%3e%3c/path%3e%3cpath d='M905.99 522.28 a27.93 27.93 0 1 0 55.86 0 a27.93 27.93 0 1 0 -55.86 0z' stroke='%23d3b714'%3e%3c/path%3e%3cpath d='M878.82 241.19L928.56 241.19L928.56 290.93L878.82 290.93z' fill='%23e73635'%3e%3c/path%3e%3cpath d='M620.67 145.35L648.93 145.35L648.93 173.61L620.67 173.61z' stroke='%23037b0b'%3e%3c/path%3e%3cpath d='M941.34 228.22a0.43 0.43 0 1 0-0.84 0.22z' stroke='%23e73635'%3e%3c/path%3e%3cpath d='M142.87 85.55 a19.16 19.16 0 1 0 38.32 0 a19.16 19.16 0 1 0 -38.32 0z' stroke='%23037b0b'%3e%3c/path%3e%3cpath d='M329.59 523.81L381.54 523.81L381.54 575.76L329.59 575.76z' fill='%23e73635'%3e%3c/path%3e%3cpath d='M794.97 148.48 a17.04 17.04 0 1 0 34.08 0 a17.04 17.04 0 1 0 -34.08 0z' stroke='%23d3b714'%3e%3c/path%3e%3cpath d='M1144.93 501.44a3.73 3.73 0 1 0-6.24 4.09z' stroke='%23e73635'%3e%3c/path%3e%3cpath d='M1.85 326.79L6.52 326.79L6.52 331.46L1.85 331.46z' fill='%23d3b714'%3e%3c/path%3e%3cpath d='M1161.24 414.19L1165.88 414.19L1165.88 445.98L1161.24 445.98z' fill='%23e73635'%3e%3c/path%3e%3cpath d='M1208.54 452.49a10.52 10.52 0 1 0 10.48 18.25z' fill='%23e73635'%3e%3c/path%3e%3cpath d='M1356.2 405.97a49.06 49.06 0 1 0 59.45-78.06z' stroke='%23037b0b'%3e%3c/path%3e%3cpath d='M569.26 204.09 a47.56 47.56 0 1 0 95.12 0 a47.56 47.56 0 1 0 -95.12 0z' stroke='%23d3b714'%3e%3c/path%3e%3cpath d='M955.59 150.05a43.84 43.84 0 1 0-65.58-58.2z' stroke='%23e73635'%3e%3c/path%3e%3cpath d='M827.49 284.25L864.82 284.25L864.82 321.58L827.49 321.58z' stroke='%23d3b714'%3e%3c/path%3e%3cpath d='M718.92 539.64 a45.27 45.27 0 1 0 90.54 0 a45.27 45.27 0 1 0 -90.54 0z' fill='%23d3b714'%3e%3c/path%3e%3cpath d='M1253.87 200.13L1273.78 200.13L1273.78 220.04L1253.87 220.04z' fill='%23e73635'%3e%3c/path%3e%3cpath d='M365.3 8.63L376.7 8.63L376.7 16.04L365.3 16.04z' stroke='%23e73635'%3e%3c/path%3e%3cpath d='M691.18 292.33a16.5 16.5 0 1 0 0.63-33z' fill='%23037b0b'%3e%3c/path%3e%3cpath d='M224.16 500.66L270.06 500.66L270.06 546.56L224.16 546.56z' fill='%23e73635'%3e%3c/path%3e%3cpath d='M174.91 13.36 a25.6 25.6 0 1 0 51.2 0 a25.6 25.6 0 1 0 -51.2 0z' stroke='%23037b0b'%3e%3c/path%3e%3cpath d='M1396.28 502.2 a16.58 16.58 0 1 0 33.16 0 a16.58 16.58 0 1 0 -33.16 0z' fill='%23037b0b'%3e%3c/path%3e%3cpath d='M1337.35 217.49L1380.42 217.49L1380.42 268.22L1337.35 268.22z' fill='%23d3b714'%3e%3c/path%3e%3cpath d='M429.47 162.98a54.75 54.75 0 1 0 89.11 63.64z' fill='%23d3b714'%3e%3c/path%3e%3cpath d='M56.44 369.3L67.51 369.3L67.51 397.03L56.44 397.03z' stroke='%23e73635'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1000'%3e%3crect width='1440' height='560' fill='white'%3e%3c/rect%3e%3c/mask%3e%3c/defs%3e%3c/svg%3e");

}
:focus {
  outline-color: var(--pink);
}

Components

components/BaseFilter.css

 .filter-nav button {
    background: none;
    border: none;
    color: #bbb;
    outline: none;
    font-size: 15px;
    text-transform: uppercase;
    margin-right: 10px;
    letter-spacing: 1px;
    font-weight: bold;
    cursor: pointer;
  }
  .filter-nav button.active {
    color: goldenrod;
  }

components/BaseFilter.jsx

import React from "react";
import './BaseFilter.css';
const BaseFilter = (props) => {

    <nav className="filter-nav">
      <button
        onClick={() => props.onUpdate('all')}
        className={props.current === 'all' ? 'active' : ''}
      >
        View all
      </button>

      <button
        onClick={() => props.onUpdate('completed')}
        className={props.current === 'completed' ? 'active' : ''}
      >
        Completed
      </button>

      <button
        onClick={() => props.onUpdate('pending')}
        className={props.current === 'pending' ? 'active' : ''}
      >
        Pending
      </button>
    </nav>
  );
};

export default BaseFilter;

components/ExerciseItem.jsx

import React from 'react'
import { Link } from 'react-router-dom';

import './ExerciseItem.css';

function ExerciseItem(props) {

  const performExerciseDeletion = () => {

    fetch(`http://localhost:3111/exercises/${props.exercise.id}`, {
      method: 'DELETE',
    })
      .then(() => props.onDeleteExercise(props.exercise.id))
      .catch((err) => console.log(err));

  };

  const performExerciseCompletion = () => {
    fetch(`http://localhost:3111/exercises/${props.exercise.id}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ complete: !props.exercise.complete }),
    })
      .then(() => props.onCompleteExercise(props.exercise.id))
      .catch((err) => console.log(err));

  };
  const classes = ['exercise'];
  if (props.exercise.complete) {
    classes.push('complete');
  }
  return (
    <div className={classes.join(' ')}>
      <div className="actions">
        <h4>{props.exercise.title}</h4>
        <div className="buttons">
          <button onClick={performExerciseDeletion}>Delete</button>
          <Link to={`/exercises/${props.exercise.id}/edit`}>Edit</Link>
          <button onClick={performExerciseCompletion}>Toggle</button>
        </div>
      </div>
      <div className="details">
        <p>{props.exercise.details}</p>
      </div>
    </div>
  );
}

export default ExerciseItem;

components/ExerciseItem.css

.exercise {
  margin: 20px auto;
  background: white;
  padding: 10px 20px;
  border-radius: 10px;
  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
  border-left: 10px solid var(--pink);
}
.exercise:hover {
  box-shadow: 0 16px 32px 0 rgba(0,0,0,0.9);
}
h3 {
  cursor: pointer;
}
.actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.exercise.complete {
  border-left: 10px solid var(--green);
}
.buttons {
  display:flex;
  flex-direction: column;
}
.buttons button,a {
 color: white;
  background: var(--pink);
  padding: 0.5rem;
  border: 0;
  border: 2px solid transparent;
  text-decoration: none;
  font-weight: 600;
  font-size:2rem;
  margin-bottom: 5px;
   border-radius: 6px;

}
.buttons button:hover, a:hover, button:active, a:active {
  background: rgb(225, 127, 143);
}
h4 {
  transform: skew(-21deg);
  background: var(--golden)
}

components/ExercisesList.css

.exercises-list {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  max-width: 600px;
  margin: 0 auto;
  color: #555;
}

components/ExercisesList.jsx

import React from 'react'
import ExerciseItem from './ExerciseItem';
import './ExercisesList.css'

function ExercisesList(props) {

  if (props.exercises.length === 0) return null;

  return (
    <div className="exercises-list">
      {props.exercises.map((exercise) => (
        <ExerciseItem
          key={exercise.id}
          exercise={exercise}
          onCompleteExercise={props.onCompleteExercise}
          onDeleteExercise={props.onDeleteExercise}
        />
      ))}
    </div>
  );

}

export default ExercisesList;

components/Navbar.css

.main-nav {
    text-align: center;
    margin: 40px auto;
  }
  .main-nav a{
    display: inline-block;
    text-decoration: none;
    margin: 0 10px;
    color: goldenrod;
    font-size: 20px;

  }
  .active-style {
    border-bottom: 2px solid goldenrod;
    padding-bottom: 4px;
  }

components/Navbar.jsx

import React from 'react'
import './Navbar.css';
import { NavLink } from "react-router-dom";

function Navbar() {
  return (
     <nav className="main-nav">
      <NavLink activeClassName="active-style" to="/home">
        Home
      </NavLink>
      <NavLink activeClassName="active-style" to="/create-exercise">
        Create Exercise
      </NavLink>
     </nav>
  );
}

export default Navbar

Pages (Views)

Create a folder for pages where we will house all our page components.

pages/HomePage.jsx

import React, { useState, useEffect  } from 'react';
import BaseFilter from '../components/BaseFilter';
import ExercisesList from '../components/ExercisesList';

const HomePage = () => {
  const [exercises, setExercises] = useState([]);
  const [currentFilter, setCurrentFilter] = useState('all');

  const updateFilterHandler = (newFilter) => {
    setCurrentFilter(newFilter);
  };

  const deleteExerciseHandler = function (id) {
    const patchedExercises = exercises.filter((exercise) => exercise.id !== id);
    setExercises(patchedExercises);
  };

  const completeExerciseHandler = function (id) {
    const clonedExercises = [...exercises];
    const currentExerciseIndex = clonedExercises.findIndex(
      (exercise) => exercise.id === id
    );
    const currentExercise = clonedExercises[currentExerciseIndex];
    currentExercise.complete = !currentExercise.complete;
    setExercises(clonedExercises);
  };


  useEffect(() => {
      try {
        const response = await fetch('http://localhost:3111/exercises');
        const fetchedExercises = await response.json();
        console.log(fetchedExercises);
        setExercises(fetchedExercises);

      } catch (error) {

        console.log(error);
      }
    }

    fetchExercises();

  }, []);


  let jsx = (
    <ExercisesList
      exercises={exercises}
      onCompleteExercise={completeExerciseHandler}
      onDeleteExercise={deleteExerciseHandler}
    />
  );
  if (currentFilter === 'completed') {
    jsx = (
      <ExercisesList
        exercises={exercises.filter((exercise) => exercise.complete)}
        onCompleteExercise={completeExerciseHandler}
        onDeleteExercise={deleteExerciseHandler}
      />
    );
  } else if (currentFilter === 'pending') {
    jsx = (
      <ExercisesList
        exercises={exercises.filter((exercise) => !exercise.complete)}
        onCompleteExercise={completeExerciseHandler}
        onDeleteExercise={deleteExerciseHandler}
      />
    );
  }
  return (
    <div>
      <BaseFilter 
        onUpdate={updateFilterHandler} 
        current={currentFilter} />
      {jsx}
    </div>
  );
};

export default HomePage;

pages/CreateExercise.css

form  {
    padding: 20px;
    border-radius: 10px;
    text-align: center;
  }
  label {
    display: block;
    color: goldenrod;
    text-transform: uppercase;
    font-size: 14px;
    font-weight: bold;
    letter-spacing: 1px;
    margin: 20px 0 10px 0
  }
  input {
    padding: 10px;
    width: 400px;
    max-width: 100%;
    box-sizing: border-box;
    border: 1px solid var(--grey);
  }
  textarea {
    padding: 10px;
    max-width: 100%;
    width: 400px;
    box-sizing: border-box;
    border: 1px solid var(--grey);

  }
  form button {
    display: block;
    margin: 20px auto;
    background: goldenrod;
    color: white;
    padding: 10px;
    border-radius: 6px;
    font-size: 16px;
    border: 2px solid transparent;
  }

pages/CreateExercise.jsx

import React, { useState } from 'react';
import './CreateExercise.css';
import { useHistory } from 'react-router-dom';

const CreateExercise = () => {

  const [exercise, setExercise] = useState({
    title: '',
    details: ''
  })

  const history = useHistory();

  const handleChange = event => {
    setExercise({
      ...exercise,
      [event.target.name] : event.target.value
    });
  }

  const handleExerciseCreation = (event) => {

    event.preventDefault();

    const newExercise = {
      title: exercise.title,
      details : exercise.details,
      complete: false,
      id: Math.floor(Math.random() * 10000),
    };

    console.log(newExercise);

    fetch('http://localhost:3111/exercises', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(newExercise)
    }).then(() => {
      history.push('/home');
    }).catch(err => console.log(err))
  };

  return (
    <form  onSubmit={handleExerciseCreation}>
      <label>Title</label>
      <input type="text" onChange={handleChange} name="title" value={exercise.title} maxLength="15" required />
      <label>Details</label>
      <textarea value={exercise.details} name="details"  onChange={handleChange} required></textarea>
      <button>Add Exercise</button>
    </form>
  );
};

export default CreateExercise;

pages/EditExercise.jsx

import React, { useState, useEffect } from 'react';
import './CreateExercise.css';
import { useHistory, useParams } from 'react-router-dom';

const EditExercise = () => {

  const [exercise, setExercise] = useState({
    title: '',
    details: '',
  });

  const params = useParams();
  const exerciseId = params.id;
  const history = useHistory();

  const handleChange = (event) => {
    setExercise({
      ...exercise,
      [event.target.name]: event.target.value,
    });
  };

  useEffect(() => {

    fetch(`http://localhost:3111/exercises/${exerciseId}`)
      .then((res) => res.json())
      .then((data) => {
         setExercise({
           title: data.title,
           details: data.details,
         });
      })
      .catch((err) => console.log(err));

  }, [exerciseId])

  const handleExerciseUpdation = (event) => {

     event.preventDefault();
     fetch(`http://localhost:3111/exercises/${exerciseId}`, {
       method: 'PATCH',
       headers: { 'Content-Type': 'application/json' },
       body: JSON.stringify(exercise),
     })
       .then(() => {
         history.push('/home');
       }).catch((err) => console.log(err));
  };

  return (
    <form onSubmit={handleExerciseUpdation}>
      <label>Title</label>
      <input
        type="text"
        onChange={handleChange}
        name="title"
        value={exercise.title}
        maxLength="15"
        required
      />
      <label>Details</label>
      <textarea
        value={exercise.details}
        name="details"
        onChange={handleChange}
        required
      ></textarea>
      <button>Update Exercise</button>
    </form>
  );
};

export default EditExercise;

So this is the code for our entire project.

PS – If you prefer learning React with a video. I do have a video on same where we create this exact project.

Check this :

Thanks for reading !

Categories
javascript reactjs

8 React Projects Every Beginner Should Try

✨8 React Projects Every Beginner Should Try

Hey everyone,

In this article, let us see 8 React Projects Every Beginner Should Try.

Introduction

React is a free and open-source front-end JavaScript library for building user interfaces or UI components. It is maintained by Facebook and a community of individual developers and companies. React can be used as a base in the development of single-page or mobile applications. In this article, let us see some cool projects that you can make to make the process of learning React extremely easy.

1. Build a Photo Gallery with React.js and Firebase

This tutorial by Net Ninja (Shaun) teaches you how to create a photo gallery using React.js backed by Firebase as a database. The tutorial not only covers the basics and fundamentals of React but also shows you how to create your own custom hooks. Highly recommended for those who are looking to learn React.

What will you learn?

1. React basics and Fundamentals
2. Creating your own Hooks

2. Build an Expense Tracker using React Hooks and Context API

In this tutorial, Brad (Traversy Media) shows how to create an Expense Tracker Application using React Hooks and Context API. He also uses one of the state management solutions – Context API

What will you learn?
1. React Basics and Fundamentals
2. Managing State using Context API

3. Build Hangman with React by CodeSTACKr

In this video we will create a hangman game using React with hooks

What will you learn?
1. React basics and Fundamentals, Hooks

4. Build an Exercise Tracker using React.js and Hooks

In this video, we will make a full Exercise Tracker Application using React.js and React Router.

What will you learn?
1. React basics and Fundamentals, Hooks

Β 5. React.js Full Crash Course with a Meetups Project

A crash course by Maximilian Schwarzmuller (Academind) that teaches you all the fundamentals of React.js. This tutorial also includes a project that uses Firebase as a database.

What will you learn?
1. React.js basics and fundamentals
2. Firebase basics

6. Build a Weather Application using React.js by Tyler Potts

Learn how to make a Weather app in React JS, this tutorial uses the Open Weather Map API to make calls to a restful api to return the current weather data. This uses the Modern Javascript Fetch AP along with React hooks and conditonals.

What will you learn?
1. React.js basics and fundamentals

7. Build a Todo Application in React.js

DevEd shows you how to create a Todo Application in React.js

What will you learn?
1. React basics and fundamentals

Β 8. How To Build A Better Spotify With React

Spotify is pretty cool, but what if you could make a better version? In this video WebDevSimplified shows you how to create a Spotify clone that not only has many of Spotify’s features but also includes lyric lookup for any song you want.

What will you learn?
1. React basics and fundamentals
2. Working with APIs
3. Hooks

So this is it for this article. Thanks for reading.
Don’t forget to leave a like if you loved the article. Also share it with your friends and colleagues.