让我们用 React 制作一个 Pokémon 主题的笔记应用程序!

2025-05-24

让我们用 React 制作一个 Pokémon 主题的笔记应用程序!

我不知道是不是有点怀旧,但我童年最美好的回忆之一就是放学后买一包薯片,打开后发现里面竟然是纯塑料金的宝可梦金币(又名TAZOS)。其实我现在还留着一个小盒子,里面装满了宝可梦金币。今天我决定把我的宝可梦记忆提升到一个新的高度——用 React 制作一个宝可梦主题的笔记应用!🐙

Tazos 图片

在这个 Pokét Book 应用程序中,我们将使用 React-Router 在组件之间导航,并利用本地浏览器存储会话来帮助我们添加笔记、待办事项、收藏网站部分和日历,同时借助 NPM 包(例如momentreact-calendareact-router-domreact-newline-to-break )来提升我们的 React 技能!本教程的主要目的是帮助我们开始使用本地存储并进一步提升我们的 React 技能!

现在,我建议你跟我一起写代码,因为自己动手写代码比照着写要好得多,这样可以建立肌肉记忆。等你准备好了,我们就开始吧——未来的 React 大师!😉

项目的所有解释都通过注释包含在代码本身中,但如果您遇到困难或想要查看我的 CSS 文件、使用图像或自定义字体,请在我的GitHub 存储库中查看

想要在制作之前先测试一下吗?那就在Heroku上测试一下吧。

预设置 - 安装软件包

要完全按照我的方式(或按照您想要的方式)完成此项目,您需要在您喜欢的命令行中执行以下操作:

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

我们刚刚安装的软件包(除了 bootstrap 之外)将帮助我们执行以下操作:

  • react-newline-to-break:将带有换行符(“\n”)的字符串转换为无错误/警告的 React 组件。
  • moment:一个用于解析、验证、操作和格式化日期的 JavaScript 日期库。
  • react-calender:适用于您的 React 应用程序的终极日历。
  • react-router-dom: React Router 的 DOM 绑定。

步骤 1 - 初始设置

设置 Index.js 以包含本项目所需的引导模块。另外,前往FontAwesome并将 CDN 添加到 index.html 标头,以便我们稍后使用图标。

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

话虽如此,您可以按如下方式编辑您的 App.js 文件,并在您的文件夹中创建以下文件夹和文件./src

components (folder)

  • Calender.js(组件文件)
  • EditNotes.js(组件文件)
  • Favorites.js(组件文件)
  • ListNotes.js(组件文件)
  • NewNotes.js(组件文件)
  • Notes.js(组件文件)
  • ToDo.js(组件文件)

layout (folder)

  • Header.js(布局文件)
  • Clips.js(布局文件)

更新App.js如下以合并我们新创建的文件:

//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

第 2 步 - 布局文件

在本节中,我们将创建项目中最不重要的文件,以便于后续工作。这些只是 UI 组件,所以最好先创建它们(至少对我来说是这样),这样我们就可以更专注于项目的功能。

不要忘记从我的 GitHub复制我的 CSS 代码项目所需的自定义字体,这样您在编译时就不会出现错误(否则只需添加您自己的样式或将其删除)!

因此,在Header.js文件中进行以下更改:

//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

然后对您的进行以下更改Clips.js并保存:

//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

步骤 3 - 注释部分

要开始使用我们的注释部分,我们首先需要对我们的进行以下更改,ListNotes.js以便我们可以列出我们将在主 Notes.js 组件上显示的注释。

//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

然后,为了能够向我们的 Notes.js 组件添加新注释,我们需要在NewNotes.js文件中执行以下操作:

//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

接下来是通过EditNotes.js文件在我们的主 Notes.js 组件上编辑预先添加或新注释的选项。

//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

现在我们已经创建了允许添加、编辑和列出笔记的组件,我们可以Notes.js按如下方式更新主组件。与此同时,我们还将添加根据笔记 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

完成部分后,您应该会得到类似这样的结果:
图像

步骤 4 - 待办事项部分

现在我们已经添加了笔记部分,是时候转到待办事项列表了。现在,在我们的ToDo.js组件中,我们将能够添加新的待办事项列表项,将其标记为已完成,取消标记,甚至删除它们。

作为一项挑战,您可以更新它以便与本地存储一起使用,就像我们在 Notes.js 部分中所做的那样!

//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

完成部分后,您应该会得到类似这样的结果:
图像

第 5 步 - 收藏夹部分

我们的收藏夹部分的功能与我们的 ToDo.js 文件非常相似,只是增加了访问收藏夹的功能。

作为额外的奖励挑战,您可以更新它以与本地存储一起使用,就像我们在 Notes.js 部分中所做的那样!

打开Favorites.js文件并执行以下操作:

//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

完成部分后,您应该会得到类似这样的结果:
图像

步骤 6 - 日历部分

我们快要完成了,还有什么比添加日历更好的方法来完善我们的笔记应用呢?现在,我们使用上面安装的 calendarnpm 包来渲染日历。这是一个简洁的包,因为你无需编写任何代码就可以查看周、月、年,甚至十年!

在文件中Calender.js

//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

完成部分后,您应该会得到类似这样的结果:
图像

步骤 7 - 将其结合在一起

现在您已经创建了所有组件,并添加了必要的 CSS 样式,是时候测试我们的应用程序了。我经常在项目创建期间这样做来测试我的代码,但在本教程中,我们只在最后测试它——无论您想怎么做,都由您决定!使用以下命令运行您的项目:

npm start
Enter fullscreen mode Exit fullscreen mode

你应该得到类似这样的结果:
最后注意保留应用程序图像

恭喜你完成了本教程。完成后,请将其部署到 GitHub 并休息一下。你学到了什么新东西吗?你会做哪些不同的尝试?请在下方评论区留言告诉我!😊

文章来源:https://dev.to/christinec_dev/let-s-make-a-pokemon-themed-note-keeping-app-in-react-2afg
PREV
通过制作 RPG 游戏来学习 Godot 4 - 第一部分:项目概述和设置 🤠
NEXT
如何写出好的提交信息