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! 🐙
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
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();
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;
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;
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;
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;
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;
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;
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;
You should get something like this upon section completion:
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;
You should get something like this upon section completion:
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;
You should get something like this upon section completion:
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>
)
}
You should get something like this upon section completion:
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
You should get something similar to this:
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!😊
Top comments (11)
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 mustcalendar
react-calender
->react-calendar
Thanks, I fixed it!
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?
Hey Yevazn, that sounds good to me! Let me know if you need anything, and thank you. 😊
This is the coolest project I have ever seen.
Lit🔥🔥🔥
And Thanks for sharing this👏
Thank you, and it's a pleasure! 😊
Cute! Where's the Charizard tho?
He was, uhm, limited edition? 😂
Just add video functionality and I can see this replacing Google Workspaces.
Soon as I saw the tazos I knew it'd be a great post. Hello fellow SA dev 👋
really like it, a cool idea would be to add some Pokémon on the background.