DEV Community

Cover image for Let's Make a Pokémon Themed Note Keeping App in React!
christine
christine

Posted on • Updated on

Let's Make a Pokémon Themed Note Keeping App in React!

I don't know if I'm just feeling a bit nostalgic, but one of my favorite childhood memories was buying a packet of chips after school and opening it just to find pure plastic gold in the form of Pokémon Coins - or TAZOS. I actually still have a little box full of them, and today I decided to take my Pokémon memories to the next level - by making a Pokémon-themed note-keeping app with React! 🐙

Tazos Image

In this Pokét Book application, we will use React-Router to navigate between our components, and make use of Local Browser Storage Sessions to help us add notes, to-do-list items, a favorite websites section, and a calendar along with the help of NPM Packages such as moment, react-calenda, react-router-dom, and react-newline-to-break. The main purpose of this tutorial is to get us started with Local Storage and further our React skills!

Now, I would like to suggest that you code along with me because it is best to type in the code yourself than to copy it because it builds up that muscle memory. When you're ready, let's get started - future React Master! 😉

All explanations for the project are in the code itself via the comments, but if you get stuck or want to view my CSS file, use the images or custom fonts, check it out on my GitHub Repository.

Want to test it before you make it? Test it out on Heroku.

Pre Setup - Installing Packages

To complete this project exactly as (or however you want) I did, you will need to do the following in your preferred command line:

npx create-react-app note-keeper
npm i react-bootstrap bootstrap moment react-calendar react-newline-to-break react-router-dom --save
cd note-keeper
Enter fullscreen mode Exit fullscreen mode

The packages (apart from bootstrap) that we just installed will help us do the following:

  • react-newline-to-break: Converts your strings with newlines ("\n") to error/warning-free React components.
  • moment: A JavaScript date library for parsing, validating, manipulating, and formatting dates.
  • react-calender: Ultimate calendar for your React app.
  • react-router-dom: DOM bindings for React Router.

Step 1 - Initial Setup

Set up your Index.js to contain the bootstrap modules that we will need for this project. Also, head over to FontAwesome and add your CDN to your index.html header so that we can use the icons later on.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

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

reportWebVitals();
Enter fullscreen mode Exit fullscreen mode

This being said, you can edit your App.js file as follows and create the following folders and files in your ./src folder:

components (folder)

  • Calender.js (components file)
  • EditNotes.js (components file)
  • Favorites.js (components file)
  • ListNotes.js (components file)
  • NewNotes.js (components file)
  • Notes.js (components file)
  • ToDo.js (components file)

layout (folder)

  • Header.js (layout file)
  • Clips.js (layout file)

Update App.js as follows to incorporate our newly created files:

//App.js
import React from 'react';
//our component files
import NotesApp from './components/Notes';
import Main from './components/ToDo';
import CalenderApp from './components/Calender';
import FavoriteLinks from './components/Favorites';
import Header from './layout/Header';
import Clip from './layout/Clips';
//Our Router components for DOM navigation
import { BrowserRouter, Route } from 'react-router-dom';
//Our React-Bootstrap Components
import {Container, Col, Row} from 'react-bootstrap';

//main App component
function App() {
  return (
    <div className="App">
      <Container>
        <Row>
        <header className="App-header">
              //will show our clip image (optional add-in)
              <Clip /> 
          </header>
          <header className="App-header">
              //will show our page title
              <Header /> 
          </header>
        </Row>
        <Row>
            <Col className="col-12 col-md-6 col-lg-6"> 
              //will show our notes section
              <BrowserRouter>
                <Route path="/" component={NotesApp}/> 
              </BrowserRouter>
            </Col>
            <Col className="col-12 col-md-6 col-lg-6">  
              //will show our to-do section              
              <BrowserRouter>
                <Route path="/" component={Main}/> 
              </BrowserRouter>
            </Col>
        </Row>
        <Row>
          <Col className="col-12 col-md-6 col-lg-6"> 
            //will show our favorites section
            <BrowserRouter>
              <Route path="/" component={FavoriteLinks}/> 
            </BrowserRouter>
          </Col>
          <Col className="col-12 col-md-6 col-lg-6"> 
            //will show our calender section
            <BrowserRouter>
                <CalenderApp />  
            </BrowserRouter>
          </Col>
        </Row>
      </Container>
    </div>
  );
}

//exports the App component to be used in index.js
export default App;
Enter fullscreen mode Exit fullscreen mode

Step 2 - Layout Files

For this section, we will create our least-important files for the project just to get it out of the way. These are just UI components, so it's best to do it first (at least for me) so that we can focus on the functionality of our project more.

Don't forget to copy my CSS code and the custom fonts needed for the project from my GitHub so that you don't get errors upon compilation (otherwise just add your own styling or remove it)!

So, in the Header.js file, make the following changes:

//Header.js 
import React from 'react';
import {Container, Row} from 'react-bootstrap';

function Header() {
    return (
        <Container>
            <Row>
                <div className="Header">
                    <h1 className="app-title">
                        //copy the .pixels CSS from my github
                        <span className='pixels'> pxn </span>
                        Poket Book
                        <span className='pixels'> cli </span>
                    </h1>           
                </div>
            </Row>
        </Container>
    );
  }

//Exports Header Component to be used in app.js
export default Header;
Enter fullscreen mode Exit fullscreen mode

And then make the following changes to your Clips.js and save:

//Clips.js (optional)
import React from 'react';
import {Container, Row} from 'react-bootstrap';

function Clips() {
    return (
        <Container>
            <Row>
                <div className="clip">
                    <div className="clip">
                        <img src="css/images/clip.png" alt=""></img>
                    </div>      
                </div>
            </Row>
        </Container>
    );
  }

//Exports Header Component to be used in app.js
export default Clips;
Enter fullscreen mode Exit fullscreen mode

Step 3 - Notes Section

To get started with our notes section, we will need to first make the following changes to our ListNotes.js so that we can list the notes that we will display on our main Notes.js component as we go.

//ListNotes.js 
import React, {Component} from 'react';
import {NavLink} from 'react-router-dom';
import moment from 'moment';
//moment is a JavaScript date library for parsing, validating, manipulating, and formatting dates. 

//we use a class component because our notes will consist of states and inheritance from a parent component which will pass properties down the functional component via props.
class ListNotes extends Component {
    //will render date to be displayed of note that was last added/edited
    renderFormattedDate(date){
        return moment(date).format('DD MMM YYYY');
    }
    render() {
        //if there are no notes to list, we will display a div with a message
        if (!this.props.notes || this.props.notes.length === 0) {
            return (<div className="no-notes">Oops! It seems that you have no notes. Try adding one? 😊</div>)
        }
        //if there are notes to list, we will display a div with the notes
        const listItems = this.props.notes.map((note) =>
                //nav link to the div of respective note without displaying the id
                <NavLink activeClassName='active' to={`/note/${note.id}`}
                      className="list-group-item"
                      key={note.id.toString()}
                      onClick={this.props.viewNote.bind(this, note.id)}>
                    {/*Show note title*/}
                    <div className="text-truncate primary">{note.title}</div>
                    {/*Show note date*/}
                    <div className="font-weight-light font-italic small">{this.renderFormattedDate(note.date)}</div>
                </NavLink >
        );
        //Displays the notes as a list
        return (<ul className="list-group">{listItems}</ul>);
    }
}

//exports for use in other files
export default ListNotes;
Enter fullscreen mode Exit fullscreen mode

Then, to be able to add new notes to our Notes.js component, we will need to do the following in the NewNotes.js file:

//NewNotes.js
import React from 'react';
import { Redirect } from 'react-router';
//React Router is a collection of navigational components. 

//this will hide our note id div from showing on the note screen
const divStyle = {
    display: 'none'
};

//we use a class component because our notes will consits of states and inheritance from a parent component which will pass properties down the functional component via props.
class NewNotes extends React.Component {

        //We use a constructor to set the initial state of the class object
        constructor(props) {
        super(props);
        //we set the initial state of the note nav to false, ie. there will be no notes to show thus no notes to "redirect" to when clicked
        this.state = {
            redirect: false
        };
        //we bind the components to our event handlers to be executed
        this.saveNote = this.saveNote.bind(this);
        this.deleteNote = this.deleteNote.bind(this);
    }

    //saveNote Event Handler which will save a new note
    saveNote(event) {
        //the preventDefault() option is added to stop the page from reloading upon submitting a note
        event.preventDefault();
        //if the Title of the note is empty, we validate it via alert
        if (this.title.value === "") {
            alert("Title is needed");
        } else {
             //we assign each note with an id, title, desc and image upon submit
            const note = {
                id: Number(this.id.value),
                title: this.title.value,
                description: this.description.value
            }
             //we set the new state of the note nav to true so that it can "redirect" to the note when clicked
            this.props.persistNote(note);
            this.setState({
                redirect: true
            });
        }
    }

    //deleteNote Event Handler which will delete(cancel the addition) a new note
    deleteNote(event) {
        //testing purposes only
        console.log('deleteNote');
        //the preventDefault() option is added to stop the page from reloading upon submitting a note
        event.preventDefault();
        //we remove the note by deleting the respective id (note key)
        this.props.deleteNote(this.props.note.id);
    }

    //Switch between and then render(show) note titles, ie. either add a note or edit an existing note title.
    renderFormTitleAction() {
        return (this.props.note.id !== undefined) ? "Edit Note" : "New Note";
    }

    //Render(show) save/delete note buttons for a new or existing note.
    renderFormButtons() {
        //if the note.id exists, then we can either delete or edit that note
        if (this.props.note.id !== undefined) {
            return (<div>
                { /* Show the save button to edit note */}
                <button type="submit" className="btn btn-success float-right">Add Note</button>
                { /* Show the delete button to delete note */}
                <button onClick={this.deleteNote} className="btn btn-danger">Delete Note</button>
            </div>);
        }
        return (
            /* Show the add button to save a new note */
            <button type="submit" className="btn btn-success float-right">Add Note</button>
        );
    }

    render() {

        //existing note redirection
        if (this.state.redirect) {
            //if the note doesn't exist, we return to main "/"
            if (!this.props.note) {
                return <Redirect push to="/"/>;
            }
            //route to an existing note upon redirect, ie. note id: 1 will redirect to http://localhost:3000/note/1
            return <Redirect push to={`/note/${this.props.note.id}`}/>;
        }

        return (
            <div className="card">
                <div className="card-header">
                    {/* This will render the correct titles depending on if there are existing notes or not*/}
                    {this.renderFormTitleAction()}
                </div>
                <div className="card-body">
                    {/* Form that allows us to add a new note*/}
                    <form ref="NewNotes" onSubmit={this.saveNote}>
                        <div className="form-group">
                            {/* Renders a new note id (divStyle will hide this from view)*/}
                            <p className="note_id">
                                <input className="form-control" style={divStyle} disabled ref={id => this.id = id} defaultValue={this.props.note.id}/>
                            </p>
                            {/* Renders a new note title */}
                            <p className="note_title">
                                <label className="noteTitle">Title</label>
                                <input className="form-control" ref={title => this.title = title} defaultValue={this.props.note.title} placeholder="Save Princess Peach"/>
                            </p>
                            {/* Renders a new note description*/}
                            <p className="note_desc">
                                <label className="noteDescTitle">Description</label>
                                <textarea className="form-control" rows="10" ref={description => this.description = description} defaultValue={this.props.note.description} placeholder="When Mario reaches the end of the course, remember to save Princess Peach or Luigi will! "/>
                            </p>
                        </div>
                        {/* This will render the correct buttons depending on if there are existing notes or not*/}
                        {this.renderFormButtons()}
                    </form>
                </div>
            </div>
        )
    }
}

//exports for use in other files
export default NewNotes;
Enter fullscreen mode Exit fullscreen mode

Next up is the option to edit pre-added or new notes on our main Notes.js component via the EditNotes.js file.

//EditNotes.js
import React from 'react';
import { Redirect } from 'react-router';
import moment from 'moment';
import newline from 'react-newline-to-break';
//moment is a JavaScript date library for parsing, validating, manipulating, and formatting dates. 

//class component will switch between editing and deleting note rendering states
class EditNotes extends React.Component {
    //We use a constructor to set the initial state of the class object
        constructor(props) {
        super(props);
        //we set the initial state of the note nav to false, ie. there will be no notes to show thus no notes to "redirect" to when clicked
        this.state = { 
            redirect : false
        };
        //we bind the components to our event handlers to be executed
        this.deleteNote = this.deleteNote.bind(this);
        this.editNote = this.editNote.bind(this);
    }

    //deleteNote Event Handler which will delete an existing note
    deleteNote(event){
        //the preventDefault() option is added to stop the page from reloading upon submitting a note
        event.preventDefault();
        //we remove the note by deleting the respective id (note key)
        this.props.deleteNote(this.props.note.id);
    }

    //editNote Event Handler which will update an existing note
    editNote(event){
        //the preventDefault() option is added to stop the page from reloading upon submitting a note
        event.preventDefault();
        //we edit the note by updating the respective id (note key)
        this.props.editNote(this.props.note.id);
    }

    //will render to be displayed when a new date whenever a note is edited
    renderFormattedDate(){
        return 'Last edited:' + moment(this.props.note.date).format("DD MMM YYYY [at] HH:mm");
    }

    render() {
        //if the note doesn't exist, we return to main "/"
        if (this.state.redirect || !this.props.note) {
            return <Redirect push to="/"/>;
        }

        //else we return a card with the note details
        return (
            <div className="card">
                {/*Will render the note title*/}
                <div className="card-header">
                    <h4>{this.props.note.title}</h4>
                </div>
                <div className="card-body">
                    {/*Will render the note added/last updated date*/}
                    <p className="text-center font-weight-light small text-muted">{this.renderFormattedDate()}</p>
                    {/*Will render the note description*/}
                    <p className="card-text-main">Title: {newline(this.props.note.title)}</p>
                    <p className="card-text">{newline(this.props.note.description)}</p>
                    {/*Will render the delete button*/}
                    <button onClick={this.deleteNote} className="btn btn-danger">Delete</button>
                    {/*Will render the edit button*/}
                    <button onClick={this.editNote} className="btn btn-success float-right">Edit</button>
                </div>
            </div>
        )
    }
}

//exports it for use in other files
export default EditNotes;
Enter fullscreen mode Exit fullscreen mode

Now that we have created our components that will allow us to Add, Edit and List our notes, we can update our main Notes.js component as follows. While we're at it, we will also add the functionality to view and delete notes according to their note id.

//Notes.js
import React from 'react';
import moment from 'moment';
import NewNotes from './NewNotes';
import EditNotes from './EditNotes';
import NotesList from './ListNotes';
import { Route, Link } from 'react-router-dom';

//class component will switch between displaying all existing or new note rendering states
class NotesApp extends React.Component {
        //We use a constructor to set the initial state of the class object
        constructor(props) {
        super(props);
        //will store the notes on our localStorage for storing user notes (local testing purposes)
        const notes = localStorage.getItem('notes') ? JSON.parse(localStorage.getItem('notes')) : [];
        //sets the initial state of all notes on storage base
        this.state = {
            notes: notes,
            selectedNote: null,
            editMode: false
        };
        //we bind the components to our event handlers to be executed
        this.getNotesNextId = this.getNotesNextId.bind(this);
        this.addNote = this.addNote.bind(this);
        this.viewNote = this.viewNote.bind(this);
        this.openEditNote = this.openEditNote.bind(this);
        this.saveEditedNote = this.saveEditedNote.bind(this);
        this.deleteNote = this.deleteNote.bind(this);
    }

    //Initiates the note id's that are/will be stored via the localStorage 
    getNotesNextId() {
        return this.state.notes.length > 0 ? this.state.notes[this.state.notes.length - 1].id + 1 : 0;
    }

    //we persist the fetched data as string because we get the stored value parsed as a boolean, ie. does it have notes (yes/no)
    persistNotes(notes) {
        localStorage.setItem('notes', JSON.stringify(notes));
        this.setState({notes: notes});
    }

    //we give each note an id, date and new persisted state when we add a new note and push it to the notes local array.
    addNote(note) {
        //set notes values
        note.id = this.getNotesNextId();
        note.date = moment();
        const notes = this.state.notes;
        //adds new note values
        notes.push(note);
        this.persistNotes(notes);
        this.setState({selectedNote: null, editMode: false});
    }

    //we view each note via mapping over it's id array, and when it is not found we handle it via an arror handler
    viewNote(id) {
        const notePosition = this.state.notes.findIndex((n) => n.id === id);
        //display the note on the screen
        if (notePosition >= 0) {
            this.setState({
                selectedNote: this.state.notes[notePosition], 
                editMode: false
            });
        } 
        //error handler
        else {
            console.warn('The note with the id ' + id + ' was not found. Please try again.');
        }
    }

    //we edit each note via mapping over it's id array, and when it is not found we handle it via an arror handler
    openEditNote(id) {
        const notePosition = this.state.notes.findIndex((n) => n.id === id);
        //displays the note to edit on screen
        if (notePosition >= 0) {
            this.setState({
                selectedNote: this.state.notes[notePosition], 
                editMode: true
            });
        } 
        //error handler
        else {
            console.warn('The note with the id ' + id + ' was not found. Please try again.');
        }
    }

    //we save each note via mapping over it's id array, and when it is not found we handle it via an arror handler
    saveEditedNote(note) {
        const notes = this.state.notes;
        const notePosition = notes.findIndex((n)=> n.id === note.id);
        //displays the note to add on screen
        if (notePosition >= 0) {
            note.date = moment();
            notes[notePosition] = note;
            this.persistNotes(notes);
        } 
        //error handler
        else {
            console.warn('The note with the id ' + note.id + ' was not found. Please try again.');
        }
        //updates notes to list
        this.setState({
            selectedNote: note, 
            editMode: false
        });
    }

    //we delete each note via mapping over it's id array, and when it is not found we handle it via an arror handler
    deleteNote(id) {
        const notes = this.state.notes;
        const notePosition = notes.findIndex((n)=> n.id === id);
        //deletes the note from the screen screen
        if (notePosition >= 0) {
            if (window.confirm('Are you sure you want to delete this note?')) {
                notes.splice(notePosition, 1);
                this.persistNotes(notes);
                this.setState({selectedNote: null, editMode: false});
            }
        } 
        //error handler
        else {
            console.warn('The note with the id ' + id + ' was not found. Please try again.');
        }
    }

    //initiates the values of each new note
    getEmptyNote() {
        return {
            title: "",
            description: "",
            image: ""
        };
    }

    //renders the notes list menu on the screen
    renderMenu () {
        return (
            <div className="card">
                {this.renderHeader()}
                <div className="card-body">
                    <NotesList notes={this.state.notes} viewNote={this.viewNote}/>   
                </div>
            </div>
        )
    }

    //renders the notes header on the screen
    renderHeader() {
        return (
            <div className="card-header">
                {/*renders close view*/ }
                <Route exact path="/note" render={routeProps => 
                    <Link to="/">
                        <button type="button" className="btn btn-danger">Cancel Note</button>
                    </Link> }/>
                {/*renders note view*/ }
                {["/", "/note/:id"].map(path =>
                <Route key={path} exact path={path} render={routeProps => 
                    <Link to="/note">
                        <button type="button" className="btn btn-success">New Note</button>
                    </Link>}/>
                )}
            </div>
        )
    }

    //display the notes when clicked on for editing, note and empty note views
    setMainAreaRoutes() {
        const editMode = this.state.editMode;
        return (<div>
            {/*edits either the new note or exisitn note*/ }
            {editMode ? (
                <Route exact path="/note/:id"
                       render={routeProps => <NewNotes persistNote={this.saveEditedNote} deleteNote={this.deleteNote} note={this.state.selectedNote}/>}
                    />
                ) : (
                <Route exact path="/note/:id" render={routeProps =>     
                    <EditNotes editNote={this.openEditNote} deleteNote={this.deleteNote} note={this.state.selectedNote}/>}
                />
            )}
            {/*displays if no notes can be found*/ }
            <Route exact path="/note"
                   render={routeProps =>  <NewNotes persistNote={this.addNote} note={this.getEmptyNote()}/>}
                />
        </div>)
    }

    render() {
        return (
            <div className="notesApp container-fluid">
                 <div className="card-notes-header">
                    <h2> NOTES </h2>
                </div>
                <div className="row">
                    {/*renders note list menu*/ }
                    <div className="col-12">
                        {this.renderMenu()}  
                    </div>
                    {/*renders note area menu*/ }
                    <div className="col-12">
                        {this.setMainAreaRoutes()}
                    </div>
                    </div>
            </div>
        );
    }
}

//exports for use in other files
export default NotesApp;
Enter fullscreen mode Exit fullscreen mode

You should get something like this upon section completion:
image

Step 4 - To Do Section

Now that we've added our notes section, it is time to move on to our To-Do list. Now, in our ToDo.js component, we will be able to add new to-do-list items, mark them as complete, unmark them, and even delete them.

As a challenge, you can update this to also work with Local Storage as we did in the Notes.js section!

//ToDo.js
import React from 'react';
import {Col, Row} from 'react-bootstrap';

//Initiate the ToDo function that will display our main display components, ie the list, check/uncheck button, and delete button
function Todo({ todo, index, completeTodo, unCompleteTodo, removeTodo }) {
    return (
          <div
            className="todo"
            style={{ textDecoration: todo.isCompleted ? "line-through" : "" }}
          >
          {todo.text}
          <div>
          {/*completes list*/}
          <button onClick={() => completeTodo(index)} className="btn btn-icon-check"> 
            <i className="fas fa-check-circle"></i>
          </button>
          {/*uncompletes list*/}
          <button onClick={() => unCompleteTodo(index)} className="btn btn-icon-redo"> 
          <i className="fas fa-redo"></i>
          </button>
          {/*deletes list*/}
          <button onClick={() => removeTodo(index)} className="btn btn-icon-trash"> <i className="fas fa-trash"></i> </button>
        </div>
      </div>
    );
  }

  //sets our initial state of our todo list to null
  function TodoForm({ addTodo }) {
    const [value, setValue] = React.useState("");
    const handleSubmit = e => {
      e.preventDefault();
      if (!value) return;
      addTodo(value);
      setValue("");
    };

    //returns a form to add a new todo item to our list
    return (
      <form onSubmit={handleSubmit} className="card-header-todo mb-3">
        <Row>
          <Col className="col-md-8">
            <input 
            type="text"
            className="input"
            value={value}
            onChange={e => setValue(e.target.value)
            }/>
           </Col>
           <Col className="col-md-4 btn-add">
            <button type="submit" className="btn-success">Add To-Do</button>
           </Col>
        </Row>
      </form>
    );
  }

  //Main function ties it together
  function Main() {
    //default values are passed for display purposes
    const [todos, setTodos] = React.useState([
      {
        text: "Do Some Magic With React 🔮",
        isCompleted: false
      },
      {
        text: "Ban Townies From Sims Game ❌",
        isCompleted: false
      },
      {
        text: "Water The Dead Cactus 🌵",
        isCompleted: false
      }
    ]);

    //adds a todo to the list
    const addTodo = text => {
      const newTodos = [...todos, { text }];
      setTodos(newTodos);
    };

    //checks the complete button and strikes through the text
    const completeTodo = index => {
      const newTodos = [...todos];
      newTodos[index].isCompleted = true;
      setTodos(newTodos);
    };

    //checks the uncomplete button and unstrikes through the text
    const unCompleteTodo = index => {
      const newTodos = [...todos];
      newTodos[index].isCompleted = false;
      setTodos(newTodos);
    };

    //deletes the whole list item as a whole
    const removeTodo = index => {
      const newTodos = [...todos];
      newTodos.splice(index, 1);
      setTodos(newTodos);
    };

    //renders the main ui of to do list
    return (
      <div className="todoList container-fluid">
        <div className="todo-header">
          <div className="todo-list-header">
              <h2>TO-DO </h2>
          </div>  
      </div>   
          <div className="card">
              <div className="card-body todo-body">
                  {/*form to add a new to do item*/}
                  <div className="card-todo-form">
                      <TodoForm addTodo={addTodo}/>
                  </div>
                  <div className="card-list">
                      {/*maps over todo items and instantiates functions for existing items*/}
                      {todos.map((todo, index) => (
                      <Todo
                        key={index}
                        index={index}
                        todo={todo}
                        completeTodo={completeTodo}
                        removeTodo={removeTodo}
                        unCompleteTodo={unCompleteTodo}
                      />
                    ))}   
                  </div>
              </div><div className="card-pixels-todo">
                    <span className="pixels">todos</span>
                    </div>
            </div>
      </div>
    );
  }

  //exports for use in other files
  export default Main;
Enter fullscreen mode Exit fullscreen mode

You should get something like this upon section completion:
image

Step 5 - Favorites Section

Our favorites section functions extremely similar to our ToDo.js file, it just has the added extra of visiting our favorites.

As an extra bonus challenge, you can update this to also work with Local Storage as we did in the Notes.js section!

Open up your Favorites.js file and do the following:

//Favorites.js
import React from 'react';
import {Col, Row} from 'react-bootstrap';

//Initiate the Faves function that will display our main display components, ie the link, button, and category
function Faves ({ favorite, visitFaves, index, removeFaves }) {
    return (
        <Row className="fave-link">
            {/*displays link*/}
            <Col className="col-8 favorites-p"> 
                <a href={favorite.text}>{favorite.text}</a>
            </Col>

            {/*deletes favorite*/}
            <Col className="col-4"> 
                <button onClick={() => removeFaves(index)} className="btn btn-icon-trash"> <i className="fas fa-trash"></i> </button>

                <button onClick={() => visitFaves(index)} className="btn btn-icon-redo"><i className="fas fa-globe"></i> </button>
            </Col>
        </Row>
    );
  }

    //sets our initial state of our fave list to null
    function FaveForm({ addFaves }) {
    const [value, setValue] = React.useState("");
    const handleSubmit = e => {
      e.preventDefault();
      if (!value) return;
      addFaves(value);
      setValue("");
    };

    //returns a form to add a new fave item to our list
    return (
      <form onSubmit={handleSubmit} className="mb-3">
        <Row>
          <Col className="col-md-8 ">
            <input 
            type="text"
            className="faves-input"
            value={value}
            onChange={e => setValue(e.target.value)
            }/>
          </Col>
          <Col className="col-md-4">
            <button type="submit" className="faves-input-btn">Favorite!💖</button>
          </Col>
        </Row>
      </form>
    );
  }

    //FavoriteLinks function ties it together
    function FavoriteLinks() {
    const [favorites, setFaves] = React.useState([
    //default values are passed for display purposes
      {
        text: "https://www.youtube.com"
      },
      {
        text: "https://github.com/christinec-dev"
      },
      {
        text: "https://developer.mozilla.org/"
      }
    ]);

    //adds a favorite to the list
    const addFaves = text => {
      const newFaves = [...favorites, { text}];
      setFaves(newFaves);
    };

    //deletes the favorite from list
    const removeFaves = index => {
      const newFaves = [...favorites];
      newFaves.splice(index, 1);
      setFaves(newFaves);
    };

    //deletes the favorite from list
    const visitFaves = index => {
      const newFaves = window.location.href=`{favorite.text}`;
      setFaves(newFaves);
    };

    //renders the main ui of to do list
    return (
      <div className="favorites mb-3 container-fluid">
         <div className="favorites-header">
            <h2>FAVORITE SITES</h2>
         </div>
      <div className="card">
        <div className="card-body favorites">
        <Row>
            <Col className="col-md-8">
                <h3 className="cat-header">Website</h3> 
            </Col>
            <Col className="col-md-4">
                <h3 className="cat-header">Modify</h3>
            </Col>
        </Row>
        {/*maps over todo items and instantiates functions for existing items*/}
          {favorites.map((favorite, index, category) => (
            <Faves
              key={index}
              index={index}
              favorite={favorite}
              removeFaves={removeFaves}
              visitFaves={visitFaves}
              category={category}  
            />
          ))}
        {/*form to add a new item*/}
          <div className="faves-form">
            <FaveForm addFaves={addFaves}/>
          </div>
        </div>
      </div>
      </div>
    );
  }

//exports for use in other files
export default FavoriteLinks;
Enter fullscreen mode Exit fullscreen mode

You should get something like this upon section completion:
image

Step 6 - Calendar Section

We're almost at the end, and what better way to wrap up our notes keeping app than to add a calendar? Now for this, we use the calendarnpm package installed above to render our calendar. It is a neat package because you can view the weeks, months, year(s), and even the decades without having to code anything!

In the Calender.js file:

//Calender.js
import React, {useState} from 'react'
import Calendar from 'react-calendar'
import 'react-calendar/dist/Calendar.css';

//calender that will be shown on our main page
export default function CalenderApp () {
//main date functions to initialize our date state
const [dateState, setDateState] = useState(new Date())
//changes date to current selection on calender
const changeDate = (e) => {
    setDateState(e)
}
return (
    //returns the calender as rendered cal
    <div className="container-fluid">
      <div className="favorites-header">
            <h2>CALENDER</h2>
         </div>
      <div className="calender-main">
        <div className="card">
          <div className="calender-card-body">
            <>
            <Calendar 
            value={dateState}
            onChange={changeDate}
            className="calender-body"
            />
          </>
        </div>
      </div>
    </div>
  </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

You should get something like this upon section completion:
image

Step 7 - Tying It Together

Now that you have created all the components, and added the necessary CSS styling, it is time to test our application. I do this frequently during project creation to test my code, but during this tutorials we only test it at the end - however you want to do it, is up to you! Run your project with the following command:

npm start
Enter fullscreen mode Exit fullscreen mode

You should get something similar to this:
Final Note keeping App Image

Good job for reaching the end of this tutorial. When you're done, deploy it to GitHub and take a break. Did you learn something new? What would you do different? Let me know in the comments down below!😊

Oldest comments (11)

Collapse
 
star_trooper profile image
Atharva Shirdhankar • Edited

This is the coolest project I have ever seen.
Lit🔥🔥🔥
And Thanks for sharing this👏

Collapse
 
christinec_dev profile image
christine

Thank you, and it's a pleasure! 😊

Collapse
 
victorquanlam profile image
Victor Quan Lam

Cute! Where's the Charizard tho?

Collapse
 
christinec_dev profile image
christine

He was, uhm, limited edition? 😂

Collapse
 
imagineeeinc profile image
Imagineee

really like it, a cool idea would be to add some Pokémon on the background.

Collapse
 
hrossouw42 profile image
Harmun

Soon as I saw the tazos I knew it'd be a great post. Hello fellow SA dev 👋

Collapse
 
andrewbrown profile image
Andrew Brown 🇨🇦

Just add video functionality and I can see this replacing Google Workspaces.

Collapse
 
qz11 profile image
Yevazn

Cool!

I'm from HelloGitHub, a nonprofit organization in China. I am reaching out to you to ask if it's OK for us to translate your posts, "Let's Make a Pokémon Themed Note Keeping App in React!", into Chinese and publish it on our WeChat official account. You own the copyright of course. Also your Dev.to profile is linked.

How does that sound to you?

Collapse
 
christinec_dev profile image
christine

Hey Yevazn, that sounds good to me! Let me know if you need anything, and thank you. 😊

Collapse
 
star_trooper profile image
Atharva Shirdhankar

hey @Christine Coomans

package installation command there is one change

npm i react-bootstrap bootstrap moment react-calender react-newline-to-break react-router-dom --save

The word calender inside this command must calendar

react-calender -> react-calendar

Collapse
 
christinec_dev profile image
christine

Thanks, I fixed it!