Search by

    Terakhir diperbaharui: Feb 14, 2021

    Membuat Front End #3

    Pada bagian ini kita akan menyempurnakan Front End dari aplikasi DinoTes dengan menambahkan:

    • Data
    • Navigasi antar page
    • Event Handler

    Menyimpan Data

    Ketika kita membuat sebuah note baru maka note tersebut harus disimpan di suatu tempat, ada beberapa pilihan tempat menyimpan data:

    • Component State, tapi data akan hilang ketika browser di-refresh
    • Database, ini adalah tempat paling ideal untuk menyimpan data sebuah aplikasi, tapi karena kita hanya fokus pada Front End sedangkan Database umumnya 'hidup' di server maka kita tidak akan menggunakan Database untuk saat ini
    • Local Data Storage / Database, seperti sqlite, realm adalah media penyimpanan yang bisa digunakan di sisi client, tapi kita perlu belajar konsep yang digunakan terlebih dahulu
    • Browser localStorage, media penyimpanan yang ada pada browser ini dapat kita gunakan untuk menyimpan data sementara, data tidak akan hilang selama data tidak dihapus(clear browsing data/cache)

    Kita ambil yang paling simple, yaitu localStorage.

    Cara ini tidak direkomendasikan dalam membuat aplikasi, ini hanya untuk sementara dan kita akan menggantinya dengan Database ketika membuat bagian Back End dari Dinotes.

    Navigasi antar page

    Sampai saat ini untuk mengakses Add page kita harus mengaksesnya dengan alamat http://localhost:3000/add, seharusnya hal ini bisa dilakukan dengan klik button Add New Note.

    Kita perbaiki navigasi antar page, termasuk menambahkan breadcumb untuk navigasi kembali ke Home.

    Step by step

    1.Edit Home Page

    src/pages/Home.js

    1import React from 'react';
    2import { Link } from 'react-router-dom';
    3import PageLayout from '../layouts/PageLayout';
    4import NotesList from '../components/NotesList';
    5import Container from '../components/ui/Container';
    6import Button from '../components/ui/Button';
    7
    8const HomePage = () => {
    9 return (
    10 <PageLayout>
    11 <Container>
    12 <Link to="/add">
    13 <Button>Add New Note</Button>
    14 </Link>
    15 <h1>All Notes</h1>
    16 <NotesList>Notes List</NotesList>
    17 </Container>
    18 </PageLayout>
    19 );
    20};
    21
    22export default HomePage;
    1. Menambah breadcumb untuk navigasi kembali ke page Home.

    src/pages/Add.js

    1import React from 'react';
    2import { Link } from 'react-router-dom';
    3import PageLayout from '../layouts/PageLayout';
    4import AddNoteForm from '../components/AddNoteForm';
    5import Container from '../components/ui/Container';
    6
    7const AddPage = () => {
    8 return (
    9 <PageLayout>
    10 <Container>
    11 <div>
    12 <h4>
    13 <Link to="/">Home</Link> / Add
    14 </h4>
    15 </div>
    16 <h2>Add New Note</h2>
    17 <AddNoteForm />
    18 </Container>
    19 </PageLayout>
    20 );
    21};
    22
    23export default AddPage;

    src/pages/Edit.js

    1import React from 'react';
    2import { Link } from 'react-router-dom';
    3import PageLayout from '../layouts/PageLayout';
    4import EditNoteForm from '../components/EditNoteForm';
    5import Container from '../components/ui/Container';
    6
    7const EditPage = () => {
    8 return (
    9 <PageLayout>
    10 <Container>
    11 <div>
    12 <h4>
    13 <Link to="/">Home</Link> / Edit{' '}
    14 </h4>
    15 </div>
    16 <h1>Edit Note</h1>
    17 <EditNoteForm />
    18 </Container>
    19 </PageLayout>
    20 );
    21};
    22
    23export default EditPage;

    Menyimpan notes

    1. Edit src/components/AddNoteForm.js.
    1import React, { useState } from 'react';
    2import { Form, FormGroup, Label, Input, TextArea } from '../components/ui/Form';
    3import { v4 as uuidv4 } from 'uuid';
    4import Button from '../components/ui/Button';
    5
    6const AddNoteForm = () => {
    7 const [state, setState] = useState({ title: '', note: '' });
    8
    9 const handleTitleChange = (e) => {
    10 setState({ ...state, title: e.target.value });
    11 };
    12
    13 const handleNoteChange = (e) => {
    14 setState({ ...state, note: e.target.value });
    15 };
    16
    17 const handleSubmit = (e) => {
    18 let existing = localStorage.getItem('notes');
    19
    20 existing = existing ? JSON.parse(existing) : [];
    21
    22 const noteId = uuidv4();
    23
    24 existing[noteId] = state;
    25
    26 localStorage.setItem('notes', JSON.stringify(existing));
    27
    28 e.preventDefault();
    29 };
    30
    31 const { title, note } = state;
    32
    33 return (
    34 <Form onSubmit={handleSubmit}>
    35 <FormGroup>
    36 <Label>Title</Label>
    37 <Input type="text" name="title" value={title} onChange={handleTitleChange} />
    38 </FormGroup>
    39 <FormGroup>
    40 <Label>Note</Label>
    41 <TextArea name="note" rows="12" value={note} onChange={handleNoteChange} />
    42 </FormGroup>
    43 <FormGroup>
    44 <Button type="submit">Add</Button>
    45 </FormGroup>
    46 </Form>
    47 );
    48};
    49
    50export default AddNoteForm;

    OK, mari kita jelaskan code di atas satu-persatu.

    • Kita buat state untuk menyimpan data note, data note sendiri berupa object dengan properties title dan note
    1const [state, setState] = useState({ title: '', note: '' });
    • handleTitleChange() dan handleNoteChange() adalah event handler untuk menyimpan data dari form input ke dalam state
    1const handleTitleChange = (e) => {
    2 setState({ ...state, title: e.target.value });
    3};
    4
    5const handleNoteChange = (e) => {
    6 setState({ ...state, note: e.target.value });
    7};
    • Penjelasan handleSubmit():
    1const handleSubmit = (e) => {
    2 /*
    3 Ambil data 'notes' di dalam localStorage
    4 Simpan ke dalam variable bernama existing
    5 */
    6 let existing = localStorage.getItem('notes');
    7
    8 /*
    9 Cek jika sudah ada data bernama 'notes' di dalam localStorage
    10 Jika ada maka gunakan method JSON.parse() untuk membaca datanya
    11 Hal ini dikarenakan localStorage hanya bisa menyimpan tipe data String
    12 Jika tidak ada data di dalam 'notes' maka buat sebuah array kosong []
    13 */
    14 existing = existing ? JSON.parse(existing) : [];
    15
    16 /*
    17 Setiap note harus memiliki id unik untuk membedakan dengan data note yang lain
    18 Kita bisa menggunakan package uuid dari npm
    19 Untuk menginstallnya gunakan command => yarn add uuid
    20 */
    21 const noteId = uuidv4();
    22
    23 /*
    24 Tambahkan data note + noteId di dalam state ke array existing
    25 */
    26 existing.push({ ...state, id: noteId });
    27
    28 /*
    29 Simpan data ke localStorage dengan command localStorage.setItem()
    30 Gunakan method JSON.stringify() untuk mengubah object ke String
    31 Karena localStorage hanya bisa menyimpan tipe data String
    32 */
    33 localStorage.setItem('notes', JSON.stringify(existing));
    34
    35 // membiarkan react menghandle data yang disubmit
    36 e.preventDefault();
    37};

    Menampilkan notes

    Edit src/components/NotesList.js.

    1import React from 'react';
    2import styled from 'styled-components';
    3
    4const NotesListContainer = styled.div`
    5 display: flex;
    6 flex-direction: column;
    7 min-width: 30vw;
    8 text-align: left;
    9 margin: 1rem;
    10 padding: 1rem;
    11 border: 2px solid #a0aec0;
    12 border-radius: 5px;
    13`;
    14
    15const List = styled.ul`
    16 list-style: none;
    17`;
    18
    19const ListItem = styled.li`
    20 margin: 0.5rem;
    21`;
    22
    23const NotesList = () => {
    24 const existing = localStorage.getItem('notes');
    25
    26 const notes = existing ? JSON.parse(existing) : [];
    27
    28 const listItems = notes.map((note) => {
    29 return (
    30 <ListItem key={note.id}>
    31 <h4>{note.title}</h4>
    32 </ListItem>
    33 );
    34 });
    35
    36 return (
    37 <NotesListContainer>
    38 <List>{listItems}</List>
    39 </NotesListContainer>
    40 );
    41};
    42
    43export default NotesList;

    Pada code di atas semua notes ditampilkan dalam bentuk list.

    1const listItems = notes.map((note) => {
    2 return (
    3 <ListItem key={note.id}>
    4 <h4>{note.title}</h4>
    5 </ListItem>
    6 );
    7});

    Edit Notes

    Metode yang digunakan untuk edit adalah click-to-edit sehingga user bisa edit note dengan click title dari note tersebut.

    Jika title dari note di klik maka akan mengarah ke alamat http://localhost/edit/{noteId}.

    Step by step

    1. Edit src/components/NotesList.js
    1import React from 'react';
    2import styled from 'styled-components';
    3import { Link } from 'react-router-dom';
    4
    5const NotesListContainer = styled.div`
    6 display: flex;
    7 flex-direction: column;
    8 min-width: 30vw;
    9 text-align: left;
    10 margin: 1rem;
    11 padding: 1rem;
    12 border: 2px solid #a0aec0;
    13 border-radius: 5px;
    14`;
    15
    16const List = styled.ul`
    17 list-style: none;
    18`;
    19
    20const ListItem = styled.li`
    21 margin: 0.5rem;
    22`;
    23
    24const NotesList = () => {
    25 const existing = localStorage.getItem('notes');
    26
    27 const notes = existing ? JSON.parse(existing) : [];
    28
    29 const listItems = notes.map((note) => {
    30 return (
    31 <ListItem key={note.id}>
    32 <h4>
    33 <Link to={`/edit/${note.id}`}>{note.title}</Link>
    34 </h4>
    35 </ListItem>
    36 );
    37 });
    38
    39 return (
    40 <NotesListContainer>
    41 <List>{listItems}</List>
    42 </NotesListContainer>
    43 );
    44};
    45
    46export default NotesList;
    1. Edit src/App.js agar Route component yang mengarahkan ke page Edit bisa menerima URL parameter.
    1import React from 'react';
    2import { Route, Switch } from 'react-router-dom';
    3import styled from 'styled-components';
    4import HomePage from './pages/Home';
    5import AddPage from './pages/Add';
    6import EditPage from './pages/Edit';
    7
    8const Container = styled.div`
    9 text-align: center;
    10`;
    11
    12function App() {
    13 return (
    14 <Container>
    15 <Switch>
    16 <Route path="/add">
    17 <AddPage />
    18 </Route>
    19 <Route path="/edit/:id">
    20 <EditPage />
    21 </Route>
    22 <Route path="/">
    23 <HomePage />
    24 </Route>
    25 </Switch>
    26 </Container>
    27 );
    28}
    29
    30export default App;
    1. Edit src/components/Edit.js untuk menampilkan data dari localStorage sesuai note id.
    1import React, { useEffect, useState } from 'react';
    2import { useLocation } from 'react-router-dom';
    3import { Form, FormGroup, Label, Input, TextArea } from './ui/Form';
    4import Button from './ui/Button';
    5
    6const EditNoteForm = () => {
    7 const location = useLocation();
    8 const [state, setState] = useState({ title: '', note: '' });
    9
    10 useEffect(() => {
    11 const existing = localStorage.getItem('notes');
    12
    13 const notes = existing ? JSON.parse(existing) : [];
    14
    15 const noteId = location.pathname.replace('/edit/', '');
    16
    17 const currentNote = notes.filter((note) => note.id === noteId);
    18
    19 setState(currentNote[0]);
    20 }, []);
    21
    22 const { title, note } = state;
    23
    24 return (
    25 <Form>
    26 <FormGroup>
    27 <Label>Title</Label>
    28 <Input type="text" name="title" value={title} />
    29 </FormGroup>
    30 <FormGroup>
    31 <Label>Note</Label>
    32 <TextArea name="note" rows="12" value={note} />
    33 </FormGroup>
    34 <FormGroup>
    35 <Button type="submit">Save</Button>
    36 <Button>Delete</Button>
    37 </FormGroup>
    38 </Form>
    39 );
    40};
    41
    42export default EditNoteForm;

    Penjelasan: (sebagian code).

    1/*
    2Custom Hooks dari package react-router-dom
    3Untuk mendapatkan alamat dari page yang sedang diakses
    4*/
    5const location = useLocation();
    6
    7...
    8
    9/*
    10Proses pengambilan data dilakukan setelah DOM diupdate
    11Oleh karena itu kita menggunakan useEffect()
    12*/
    13useEffect(() => {
    14
    15 ...
    16
    17 // mengambil noteId dari location
    18 const noteId = location.pathname.replace('/edit/', '');
    19
    20 // mengambil data note yang akan diedit
    21 const currentNote = notes.filter((note) => note.id === noteId);
    22
    23 // simpan data note ke state
    24 setState(currentNote[0]);
    25
    26...
    1. Tambahkan event handler agar data yang sudah diedit bisa disimpan kembali ke dalam localStorage.

    src/components/Edit.js

    1import React, { useEffect, useState } from 'react';
    2import { useLocation } from 'react-router-dom';
    3import { Form, FormGroup, Label, Input, TextArea } from './ui/Form';
    4import Button from './ui/Button';
    5
    6const EditNoteForm = () => {
    7 const location = useLocation();
    8 const [allNotes, setAllNotes] = useState(null);
    9 const [currentNote, setCurrentNote] = useState({ title: '', note: '' });
    10
    11 useEffect(() => {
    12 const existing = localStorage.getItem('notes');
    13
    14 const notes = existing ? JSON.parse(existing) : [];
    15
    16 setAllNotes(notes);
    17
    18 const noteId = location.pathname.replace('/edit/', '');
    19
    20 const currentNote = notes.filter((note) => note.id === noteId);
    21
    22 setCurrentNote(currentNote[0]);
    23 }, []);
    24
    25 const handleTitleChange = (e) => {
    26 setCurrentNote({ ...currentNote, title: e.target.value });
    27 };
    28
    29 const handleNoteChange = (e) => {
    30 setCurrentNote({ ...currentNote, note: e.target.value });
    31 };
    32
    33 const handleSubmit = (e) => {
    34 const newNotes = allNotes.map((note) => {
    35 if (note.id === currentNote.id) {
    36 return { ...note, title: currentNote.title, note: currentNote.note };
    37 } else {
    38 return note;
    39 }
    40 });
    41
    42 localStorage.setItem('notes', JSON.stringify(newNotes));
    43
    44 e.preventDefault();
    45 };
    46
    47 const { title, note } = currentNote;
    48
    49 return (
    50 <Form onSubmit={handleSubmit}>
    51 <FormGroup>
    52 <Label>Title</Label>
    53 <Input type="text" name="title" value={title} onChange={handleTitleChange} />
    54 </FormGroup>
    55 <FormGroup>
    56 <Label>Note</Label>
    57 <TextArea name="note" rows="12" value={note} onChange={handleNoteChange} />
    58 </FormGroup>
    59 <FormGroup>
    60 <Button type="submit">Save</Button>
    61 <Button>Delete</Button>
    62 </FormGroup>
    63 </Form>
    64 );
    65};
    66
    67export default EditNoteForm;

    Penjelasan:

    • Rename State

    Untuk update data notes di localStorage yang dilakukan adalah mengambil semua data localStorage kemudian data disimpan ke dalam state bernama allNotes untuk selanjutnya menggantikan data yang lama.

    Oleh karena itu kita rename state dan menambah state yang baru untuk menyimpan data semua notes.

    1...
    2 const [allNotes, setAllNotes] = useState(null);
    3 const [currentNote, setCurrentNote] = useState({ title: '', note: '' });
    4...
    • handleSubmit()
    1...
    2 const handleSubmit = (e) => {
    3 // update data notes
    4 const newNotes = allNotes.map((note) => {
    5 if (note.id === currentNote.id) {
    6 return { ...note, title: currentNote.title, note: currentNote.note };
    7 } else {
    8 return note;
    9 }
    10 });
    11
    12 // replace data notes yang lama di local storage
    13 localStorage.setItem('notes', JSON.stringify(newNotes));
    14
    15 e.preventDefault();
    16 };
    17...

    Hapus Notes

    Tambahkan event handler untuk menghapus note ketika user klik button Delete.

    1import React, { useEffect, useState } from 'react';
    2import { useLocation, useHistory } from 'react-router-dom';
    3import { Form, FormGroup, Label, Input, TextArea } from './ui/Form';
    4import Button from './ui/Button';
    5
    6const EditNoteForm = () => {
    7 const location = useLocation();
    8 const history = useHistory();
    9 const [allNotes, setAllNotes] = useState(null);
    10 const [currentNote, setCurrentNote] = useState({ title: '', note: '' });
    11
    12 useEffect(() => {
    13 const existing = localStorage.getItem('notes');
    14
    15 const notes = existing ? JSON.parse(existing) : [];
    16
    17 setAllNotes(notes);
    18
    19 const noteId = location.pathname.replace('/edit/', '');
    20
    21 const currentNote = notes.filter((note) => note.id === noteId);
    22
    23 setCurrentNote(currentNote[0]);
    24 }, []);
    25
    26 const handleTitleChange = (e) => {
    27 setCurrentNote({ ...currentNote, title: e.target.value });
    28 };
    29
    30 const handleNoteChange = (e) => {
    31 setCurrentNote({ ...currentNote, note: e.target.value });
    32 };
    33
    34 const handleSubmit = (e) => {
    35 const newNotes = allNotes.map((note) => {
    36 if (note.id === currentNote.id) {
    37 return { ...note, title: currentNote.title, note: currentNote.note };
    38 } else {
    39 return note;
    40 }
    41 });
    42
    43 localStorage.setItem('notes', JSON.stringify(newNotes));
    44
    45 e.preventDefault();
    46 };
    47
    48 const handleDeleteNote = (e) => {
    49 const newNotes = allNotes.filter((note) => note.id !== currentNote.id);
    50
    51 setCurrentNote(null);
    52
    53 setAllNotes(newNotes);
    54
    55 localStorage.setItem('notes', JSON.stringify(newNotes));
    56
    57 history.push('/');
    58 };
    59
    60 const { title, note } = currentNote;
    61
    62 return (
    63 <Form onSubmit={handleSubmit}>
    64 <FormGroup>
    65 <Label>Title</Label>
    66 <Input type="text" name="title" value={title} onChange={handleTitleChange} />
    67 </FormGroup>
    68 <FormGroup>
    69 <Label>Note</Label>
    70 <TextArea name="note" rows="12" value={note} onChange={handleNoteChange} />
    71 </FormGroup>
    72 <FormGroup>
    73 <Button type="submit">Save</Button>
    74 <Button onClick={handleDeleteNote}>Delete</Button>
    75 </FormGroup>
    76 </Form>
    77 );
    78};
    79
    80export default EditNoteForm;

    Penjelasan: (sebagian code).

    1...
    2
    3/*
    4Custom Hooks dari package react-router-dom
    5Dapat digunakan untuk navigasi ke page atau component yang lain
    6*/
    7 const history = useHistory();
    8
    9 ...
    10
    11 const handleDeleteNote = (e) => {
    12 /*
    13 Daripada menghapus note dari data notes lama
    14 Kita membuat array baru berisi notes tanpa memasukan note yang dihapus
    15 */
    16 const newNotes = allNotes.filter((note) => note.id !== currentNote.id);
    17
    18 // kosongkan state setelah dihapus
    19 setCurrentNote(null);
    20
    21 // simpan data notes baru ke state allNotes
    22 setAllNotes(newNotes);
    23
    24 // update data di localStorage
    25 localStorage.setItem('notes', JSON.stringify(newNotes));
    26
    27 // navigasi ke page Home setelah note dihapus
    28 history.push('/');
    29 };

    Hasil Akhir