Search by

    Terakhir diperbaharui: Dec 23, 2020

    Menggunakan Redux

    Pada bagian ini kita akan tambahkan Redux pada aplikasi DinoTes untuk mengelola state.

    Tapi ada yang perlu diperhatikan disini, bahwa tanpa Redux aplikasi DinoTes bisa digunakan tanpa ada masalah.

    Redux biasa dipakai pada aplikasi React dengan jumlah component yang tidak sedikit, dimana banyak state digunakan oleh banyak component.

    Dengan menggunakan Redux semua pengelolaan state terpusat di satu tempat, meskipun begitu Redux bisa membuat aplikasi kita menjadi lebih kompleks dari yang seharusnya.

    Sehingga penggunaan Redux disini bersifat optional. Apalagi tujuan penggunaan Redux disini adalah untuk mempelajari konsep state management pada aplikasi React.

    Persiapan

    Clone repository DinoTes.

    Selain redux kita akan menggunakan dua package tambahan, yaitu redux-toolkit & react-redux.

    Package tersebut adalah package official dari Redux, direkomendasikan untuk digunakan bersamaan dengan Redux.

    Bisakah kita menggunakan Redux tanpa redux-toolkit dan react-redux?

    Jawabannya adalah bisa, hanya saja tanpa kedua tools tersebut code yang ditulis di Redux bisa menjadi sangat panjang karena harus mengikuti banyak pola atau pattern.

    Jika kamu sudah pernah menggunakan Redux sebelumnya maka kamu tahu apa maksudnya. 🙂

    Keduanya menyediakan API yang bisa dimanfaatkan tanpa harus menulis code dari awal.

    Sebagai contoh dengan menggunakan API createSlice kita tidak perlu menulis action creators untuk setiap reducer.

    Gunakan terminal atau integrated terminal pada VS Code kemudian jalankan perintah berikut untuk menginstall:

    1yarn add @reduxjs/toolkit react-redux

    Step by Step

    Langkah pertama yang kita lakukan adalah membuat 'slice'.

    Slice adalah sebuah object yang memiliki tiga bagian utama, yaitu initial state, function reducer dan nama dari slice sebagai identifikasi.

    Store pada redux adalah kumpulan dari banyak slice.

    redux slice

    Membuat Slice

    Buat sebuah folder dengan nama features di dalam folder src, dan subfolder bernama notes.

    Susunan folder:

    1...
    2|--src
    3 |--assets
    4 |--components
    5 |--features
    6 |--notes
    7 |--layouts
    8 ...

    Kemudian buat sebuah file dengan nama notesSlice.js.

    Di dalam file tersebut yang kita lakukan:

    • import function createSlice yang berfungsi untuk membuat slice
    • membuat initialState untuk data notes
    • menambahkan field reducers untuk menampung semua function reducer yang akan dibuat nanti

    src/features/notes/notesSlice.js

    1import { createSlice } from '@reduxjs/toolkit';
    2
    3const initialState = [{ id: '', title: '', note: '' }];
    4
    5const notesSlice = createSlice({
    6 name: 'notes',
    7 initialState,
    8 reducers: {}
    9});
    10
    11export default notesSlice.reducer;

    Semua reducer yang ada di dalam slice harus ditambahkan ke Redux store.

    Membuat Store

    Buat sebuah file dengan nama store.js di dalam src folder.

    Salin code berikut:

    src/store.js

    1import { configureStore } from '@reduxjs/toolkit';
    2
    3import notesReducer from '../features/notes/notesSlice';
    4
    5export default configureStore({
    6 reducer: {
    7 notes: notesReducer
    8 }
    9});

    Agar suatu aplikasi React dalam hal ini adalah DinoTes dapat menggunakan store yang baru saja dibuat kita harus memodifikasi file index.js:

    1import React from 'react';
    2import ReactDOM from 'react-dom';
    3import { BrowserRouter } from 'react-router-dom';
    4import { Provider } from 'react-redux';
    5
    6import './index.css';
    7import App from './App';
    8import * as serviceWorker from './serviceWorker';
    9
    10import store from './store.js';
    11
    12ReactDOM.render(
    13 <React.StrictMode>
    14 <Provider store={store}>
    15 <BrowserRouter>
    16 <App />
    17 </BrowserRouter>
    18 </Provider>
    19 </React.StrictMode>,
    20 document.getElementById('root')
    21);
    22
    23// If you want your app to work offline and load faster, you can change
    24// unregister() to register() below. Note this comes with some pitfalls.
    25// Learn more about service workers: https://bit.ly/CRA-PWA
    26serviceWorker.unregister();

    Update handler

    Kita perlu update semua handler yang bertanggung jawab untuk menghandle user action seperti menambah note, mengedit note dan menampilkan note.

    Update ini meliputi:

    • Membuat reducer di dalam slice untuk setiap action

    • Menambah function dispatch untuk meneruskan semua action yang didapat dari event handler di UI ke Redux store

    Hal ini dikarenakan semua state harus diupdate di dalam slice menggunakan function reducer. Dan slice berada di dalam Redux store.

    • Menambahkan middleware pada store untuk menghandle proses asynchronous yang terjadi ketika fetching data dari server/api

    Menampilkan Note

    Data notes yang berasal dari server tidak lagi ditampilkan secara langsung pada component, namun harus melalui Redux.

    Untuk menampilkan semua note tambahkan code berikut:

    src/features/notes/notesSlice.js

    1...
    2export const getAllNotes = state => state.notes;
    3...

    Buat middleware untuk menghandle proses fetching data dari server. Kita gunakan Redux toolkit API createAsyncThunk.

    1import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
    2
    3const initialState = [{ id: '', title: '', note: '' }];
    4
    5const notesSlice = createSlice({
    6 name: 'notes',
    7 initialState,
    8 reducers: {}
    9});
    10
    11export const fetchNotes = createAsyncThunk('notes/fetchNotes', async () => {
    12 const response = await fetch(`${process.env.REACT_APP_API_URL}/notes`);
    13 return response.notes;
    14});
    15
    16export const getAllNotes = (state) => state.notes;
    17
    18export const { noteAdded } = notesSlice.actions;
    19
    20export default notesSlice.reducer;

    createAsyncThunk menerima dua argument:

    • string sebagai nama untuk action types yang digenerate
    • 'payload creator', callback function yang menghasilkan Promise berisi data atau pesan kesalahan

    Kenapa harus menggunakan middleware?

    Redux store pada dasarnya tidak 'mengerti' apa itu logika asynchronous. Diantara tugas Redux store adalah melakukan proses dispatch action, update state dengan cara memanggil function reducer dan memberi tahu UI bahwa state telah berubah.

    Dengan menambahkan sebuah middleware diantara dispatch & reducer, kita bisa intercept action yang sudah di-dispatch sebelum diteruskan ke reducer.

    redux middleware

    Contoh bentuk intercept:

    • menambahkan function untuk logging

    • membuat function dispatch menerima value selain object action seperti function atau promise

      Pada kasus ini object action dihasilkan oleh middleware dan bukan berasal dari dispatch.

    Selain itu Redux menggunakan konsep functional programming, hal ini bisa dilihat dari bentuk function reducer yang harus ditulis dalam bentuk pure function sehingga tidak ada ruang untuk 'side effect'.

    Semua operasi yang memiliki 'side effect' dilakukan di dalam Redux middleware.

    Sehingga Redux middleware menjadi tempat paling cocok untuk melakukan proses asynchronous seperti fetching data dari server.

    Memantau progress dari operasi asynchronous

    Kita bisa memantau progress dari suatu proses async (API call) sebagai suatu state.

    Value dari state ini berada diantara 4 kondisi:

    • Request belum dikirim (idle)
    • Request masih dalam proses
    • Request berhasil
    • Request gagal

    Kita ubah initialState agar bisa menyimpan data dari notes, status dan error:

    1...
    2const initialState = {
    3 data: [],
    4 status: 'idle',
    5 error: null
    6}
    7...

    Isi dari state yang ada di dalam store sekarang adalah:

    • data, menyimpan semua data notes
    • status, menyimpan status dari proses async
    • error, menyimpan error jika ada error

    Kemudian update slice agar bisa menghandle hasil promise dari createAsyncThunk dengan menambahkan extraReducer.

    1import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
    2
    3const initialState = {
    4 data: [],
    5 status: 'idle',
    6 error: null
    7};
    8
    9export const fetchNotes = createAsyncThunk('notes/fetchNotes', async () => {
    10 const response = await fetch(`${process.env.REACT_APP_API_URL}/notes`);
    11 const data = await response.json();
    12 return data;
    13});
    14
    15const notesSlice = createSlice({
    16 name: 'notes',
    17 initialState,
    18 reducers: {},
    19 extraReducers: {
    20 [fetchNotes.pending]: (state, action) => {
    21 state.status = 'loading';
    22 },
    23 [fetchNotes.fulfilled]: (state, action) => {
    24 state.status = 'succeeded';
    25 state.data = action.payload;
    26 },
    27 [fetchNotes.rejected]: (state, action) => {
    28 state.status = 'failed';
    29 state.error = action.error.message;
    30 }
    31 }
    32});
    33
    34export const getAllNotes = (state) => state.notes.data;
    35
    36export default notesSlice.reducer;
    • Ketika proses fetching dimulai, status kita ubah dari 'idle' ke 'loading'
    • Jika proses fetching berhasil, kita update status ke 'succeeded'
    • Sebaliknya, jika gagal kita update status ke 'failed'

    Selanjutnya adalah menambah function dispatch pada component sebagai 'penghubung' antara component dan store.

    Setiap data atau event yang ditangkap oleh event handler di component akan diteruskan ke store oleh dispatch sebagai sebuah action.

    Update code dari src/components/NotesList.js:

    1import React, { useEffect } from 'react';
    2import styled from 'styled-components';
    3import { Link } from 'react-router-dom';
    4import { useSelector, useDispatch } from 'react-redux';
    5
    6import { getAllNotes, fetchNotes } from '../features/notes/notesSlice';
    7
    8const NotesListContainer = styled.div`
    9 display: flex;
    10 flex-direction: column;
    11 min-width: 30vw;
    12 text-align: left;
    13 margin: 1rem;
    14 padding: 1rem;
    15 border: 2px solid #a0aec0;
    16 border-radius: 5px;
    17`;
    18
    19const List = styled.ul`
    20 list-style: none;
    21`;
    22
    23const ListItem = styled.li`
    24 margin: 0.5rem;
    25`;
    26
    27const Separator = styled.hr`
    28 width: 90%;
    29 margin: -1px;
    30 background-color: #edf2f7;
    31 color: #edf2f7;
    32`;
    33
    34const NotesList = () => {
    35 const dispatch = useDispatch();
    36 const notes = useSelector(getAllNotes);
    37 const notesStatus = useSelector((state) => state.notes.status);
    38 const error = useSelector((state) => state.notes.error);
    39
    40 useEffect(() => {
    41 if (notesStatus === 'idle') {
    42 dispatch(fetchNotes());
    43 }
    44 }, [notesStatus, dispatch]);
    45
    46 let content;
    47
    48 if (notesStatus === 'loading') {
    49 content = <div>Loading...</div>;
    50 } else if (notesStatus === 'succeeded') {
    51 content = (
    52 <List>
    53 {notes.map((note) => {
    54 return (
    55 <ListItem key={note._id}>
    56 <h4>
    57 <Link to={`/edit/${note._id}`}>{note.title}</Link>
    58 </h4>
    59 <p>{note.note.slice(0, 101)}</p>
    60 <Separator />
    61 </ListItem>
    62 );
    63 })}
    64 </List>
    65 );
    66 } else if (notesStatus === 'failed') {
    67 content = <div>{error}</div>;
    68 }
    69
    70 return <NotesListContainer>{content}</NotesListContainer>;
    71};
    72
    73export default NotesList;

    Penjelasan code di atas:

    • Import function untuk menampilkan semua notes dan function untuk fetching data
    1import { getAllNotes, fetchNotes } from '../features/notes/notesSlice';
    • Mengambil data notes, status dari operasi fetching API dan error menggunakan useSelector
    1const notes = useSelector(getAllNotes);
    2const notesStatus = useSelector((state) => state.notes.status);
    3const error = useSelector((state) => state.notes.error);
    • Pengambilan (fetching) data dimulai ketika status adalah idle dan dilakukan di dalam useEffect() hooks
    1useEffect(() => {
    2 if (notesStatus === 'idle') {
    3 dispatch(fetchNotes());
    4 }
    5}, [notesStatus, dispatch]);

    Kita hanya melakukan proses fetching ketika value dari notesStatus dan dispatch berubah(line 5).

    • Conditional rendering berdasarkan status
    1if (notesStatus === 'loading') {
    2 content = <div>Loading...</div>;
    3} else if (notesStatus === 'succeeded') {
    4 content = (
    5 <List>
    6 {notes.map((note) => {
    7 return (
    8 <ListItem key={note._id}>
    9 <h4>
    10 <Link to={`/edit/${note._id}`}>{note.title}</Link>
    11 </h4>
    12 <p>{note.note.slice(0, 101)}</p>
    13 <Separator />
    14 </ListItem>
    15 );
    16 })}
    17 </List>
    18 );
    19} else if (notesStatus === 'failed') {
    20 content = <div>{error}</div>;
    21}

    Sekarang fetching data dilakukan melalu Redux middleware dan component akan dirender sesuai value dari state yang ada di dalam slice/store.

    Component atau UI dari aplikasi kini hanya bertugas untuk menampilkan perubahan sesuai dengan apa yang terjadi di dalam store.

    Menambah Note baru

    Buat sebuah middleware untuk mengirim note yang baru ke server:

    1...
    2export const addNewNote = createAsyncThunk(
    3 "notes/AddNewNote",
    4 async (initialNotes) => {
    5 const requestOptions = {
    6 method: "POST",
    7 headers: { "Content-Type": "application/json" },
    8 body: JSON.stringify(initialNotes),
    9 };
    10
    11 const response = await fetch(
    12 `${process.env.REACT_APP_API_URL}/note`,
    13 requestOptions
    14 );
    15 if (response.ok) {
    16 const data = await response.json();
    17 const noteAdded = { ...initialNotes, _id: data._id };
    18 return noteAdded;
    19 } else {
    20 return null;
    21 }
    22 }
    23);
    24...

    Untuk menampilkan data terbaru termasuk yang baru saja ditambahkan kita bisa gunakan dua cara:

    • Gunakan useSelector dan function getAllNotes untuk mengambil data dari store

      Kelebihan menggunakan cara ini adalah user dapat melihat semua note yang dimiliki dengan cepat karena memang data yang ditampilkan adalah data di sisi client.

      Tetapi kita harus selalu pastikan bahwa data yang ada di sisi client adalah data yang sama dengan yang ada di server.

    • Fetching data terbaru dari server setiap kali user membuka halaman Home

      Dengan cara kedua, data notes yang ditampilkan adalah data yang sama dengan yang ada di server.

      Tetapi cara ini membuat aplikasi DinoTes harus lebih sering mengirim network request dan jika terjadi masalah network/koneksi maka user tidak akan bisa melihat semua note yang dimiliki.

    Kita akan pilih cara kedua, data yang ditampilkan lebih konsisten dan jika terjadi masalah dengan koneksi kita bisa tampilkan pesan kesalahan kepada user.

    Yang perlu dilakukan adalah menambah function reducer yang bertugas untuk mereset status menjadi idle, hal ini akan membuat proses fetching dilakukan setiap kali user membuka halaman utama / Home.

    1...
    2 statusReset(state, action) {
    3 state.status = "idle";
    4 }
    5...

    Versi akhir dari code:

    src/features/notes/notesSlice.js

    1import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
    2
    3const initialState = {
    4 data: [],
    5 status: 'idle',
    6 error: null
    7};
    8
    9export const fetchNotes = createAsyncThunk('notes/fetchNotes', async () => {
    10 const response = await fetch(`${process.env.REACT_APP_API_URL}/notes`);
    11 const data = await response.json();
    12 return data;
    13});
    14
    15export const addNewNote = createAsyncThunk('notes/AddNewNote', async (initialNotes) => {
    16 const requestOptions = {
    17 method: 'POST',
    18 headers: { 'Content-Type': 'application/json' },
    19 body: JSON.stringify(initialNotes)
    20 };
    21
    22 const response = await fetch(`${process.env.REACT_APP_API_URL}/note`, requestOptions);
    23 if (response.ok) {
    24 const data = await response.json();
    25 const noteAdded = { ...initialNotes, _id: data._id };
    26 return noteAdded;
    27 } else {
    28 return null;
    29 }
    30});
    31
    32const notesSlice = createSlice({
    33 name: 'notes',
    34 initialState,
    35 reducers: {
    36 statusReset(state, action) {
    37 state.status = 'idle';
    38 }
    39 },
    40 extraReducers: {
    41 [fetchNotes.pending]: (state, action) => {
    42 state.status = 'loading';
    43 },
    44 [fetchNotes.fulfilled]: (state, action) => {
    45 state.status = 'succeeded';
    46 state.data = action.payload;
    47 },
    48 [fetchNotes.rejected]: (state, action) => {
    49 state.status = 'failed';
    50 state.error = action.error.message;
    51 },
    52 [addNewNote.pending]: (state, action) => {
    53 state.status = 'loading';
    54 },
    55 [addNewNote.fulfilled]: (state, action) => {
    56 state.status = 'succeeded';
    57 state.data.push(action.payload);
    58 },
    59 [addNewNote.rejected]: (state, action) => {
    60 state.status = 'failed';
    61 state.error = action.error.message;
    62 }
    63 }
    64});
    65
    66export const getAllNotes = (state) => state.notes.data;
    67
    68export const { statusReset } = notesSlice.actions;
    69
    70export default notesSlice.reducer;

    Data note yang sekarang disimpan masih kurang satu field yang penting, yaitu field id.

    id ini diperlukan untuk proses rendering pada component.

    Oleh karena itu handler pada sisi backend/api yang bertugas untuk menyimpan sebuah note baru ke dalam database perlu diupdate agar bisa mengirimkan id dari setiap note yang berhasil disimpan. Dalam hal ini adalah _id yang berasal dari MongoDB.

    Buka project dinotes-api, update file handler.js:

    1...
    2exports.addNote = async (req, res, next) => {
    3 const { notesCollection } = req.app.locals;
    4 const { title } = req.body;
    5
    6 try {
    7 if (!title) {
    8 logger.error(`${req.originalUrl} - ${req.ip} - title is missing `);
    9 throw new Error('title is missing');
    10 }
    11 // Insert data to collection
    12 const result = await notesCollection.insertOne(req.body);
    13
    14 const objResult = JSON.parse(result);
    15
    16 logger.info(`${req.originalUrl} - ${req.ip} - Data successfully saved`);
    17
    18 res.status(200).json({ message: 'Data successfully saved', _id: objResult.insertedId });
    19 } catch (error) {
    20 logger.error(`${req.originalUrl} - ${req.ip} - ${error} `);
    21 next(error);
    22 }
    23};
    24...

    Langkah selanjutnya adalah update code dari component yang bertugas untuk menambah note baru.

    src/components/AddNoteForm.js

    Update event handler handleSubmit:

    1...
    2 const handleSubmit = async (e) => {
    3 e.preventDefault();
    4
    5 try {
    6 const actionResult = await dispatch(addNewNote(state));
    7 const result = unwrapResult(actionResult);
    8 if(result) {
    9 setIsSuccess(true);
    10 } else {
    11 setIsSuccess(false);
    12 }
    13 } catch (err) {
    14 console.error("Terjadi kesalahan: ", err);
    15 setIsSuccess(false);
    16 } finally {
    17 dispatch(statusReset())
    18 }
    19 };
    20...

    Ketika dispatch dieksekusi, async thunk akan kembali dengan sebuah Promise. Untuk mengetahui value dari Promise ini apakah berhasil atau tidak kita bisa gunakan sebuah function bernama unwrapResult().

    Versi akhir code:

    src/components/addNoteForm.js

    1import React, { useState } from 'react';
    2import { unwrapResult } from '@reduxjs/toolkit';
    3import { useDispatch } from 'react-redux';
    4import { Form, FormGroup, Label, Input, TextArea } from './ui/Form';
    5import Button from './ui/Button';
    6import Message from './ui/Message';
    7import { addNewNote, statusReset } from '../features/notes/notesSlice';
    8
    9const InfoWrapper = (props) => {
    10 const { status } = props;
    11
    12 if (status !== null) {
    13 if (status === false) {
    14 return <Message type="error" text="Title harus diisi" />;
    15 }
    16 return <Message type="success" text="Data berhasil disimpan" />;
    17 }
    18 return <></>;
    19};
    20
    21const AddNoteForm = () => {
    22 const dispatch = useDispatch();
    23 const [state, setState] = useState({ title: '', note: '' });
    24 const [isSuccess, setIsSuccess] = useState(null);
    25
    26 const handleTitleChange = (e) => {
    27 setState({ ...state, title: e.target.value });
    28 };
    29
    30 const handleNoteChange = (e) => {
    31 setState({ ...state, note: e.target.value });
    32 };
    33
    34 const handleSubmit = async (e) => {
    35 e.preventDefault();
    36
    37 try {
    38 const actionResult = await dispatch(addNewNote(state));
    39 const result = unwrapResult(actionResult);
    40 if (result) {
    41 setIsSuccess(true);
    42 } else {
    43 setIsSuccess(false);
    44 }
    45 } catch (err) {
    46 console.error('Terjadi kesalahan: ', err);
    47 setIsSuccess(false);
    48 } finally {
    49 dispatch(statusReset());
    50 }
    51 };
    52
    53 const { title, note } = state;
    54
    55 return (
    56 <>
    57 <InfoWrapper status={isSuccess} />
    58 <Form onSubmit={handleSubmit}>
    59 <FormGroup>
    60 <Label>Title</Label>
    61 <Input type="text" name="title" value={title} onChange={handleTitleChange} />
    62 </FormGroup>
    63 <FormGroup>
    64 <Label>Note</Label>
    65 <TextArea name="note" rows="12" value={note} onChange={handleNoteChange} />
    66 </FormGroup>
    67 <FormGroup>
    68 <Button type="submit">Add</Button>
    69 </FormGroup>
    70 </Form>
    71 </>
    72 );
    73};
    74
    75export default AddNoteForm;

    Mengubah Note

    Edit disini meliputi update dan delete.

    Sama dengan langkah sebelumnya, kita akan lakukan hal berikut:

    • Membuat middleware / async thunk yang berkomunikasi dengan API untuk melakukan operasi update/delete
    • Membuat extraReducer yang akan bertugas untuk mengubah state pada setiap status (loading, succeeded, failed)
    • Mengupdate event handler dengan menambah method dispatch

    Update

    Buat middleware yang bertugas untuk mengirim request update ke API server:

    src/features/notes/notesSlice.js

    1...
    2export const updateExistingNote = createAsyncThunk(
    3 "notes/updateNote",
    4 async(currentNote) => {
    5 const requestOptions = {
    6 method: 'PUT',
    7 headers: { 'Content-Type': 'application/json' },
    8 body: JSON.stringify(currentNote)
    9 };
    10
    11 const response = await fetch(`${process.env.REACT_APP_API_URL}/note/${currentNote._id}`, requestOptions);
    12 if (response.ok) {
    13 return currentNote;
    14 } else {
    15 return null;
    16 }
    17 }
    18);
    19...

    Tambah extraReducer:

    1...
    2 [updateExistingNote.pending]: (state, action) => {
    3 state.status = "loading";
    4 },
    5 [updateExistingNote.fulfilled]: (state, action) => {
    6 state.status = "succeeded";
    7 const { _id, title, note } = action.payload;
    8 const existingNote = state.data.find(note => note._id === _id);
    9 if (existingNote) {
    10 existingNote.title = title;
    11 existingNote.note = note;
    12 }
    13 },
    14 [updateExistingNote.rejected]: (state, action) => {
    15 state.status = "failed";
    16 state.error = action.error.message;
    17 },
    18...

    Buat sebuah function untuk menampilkan note berdasarkan note Id:

    1...
    2export const getNoteById = (state, noteId) =>
    3 state.notes.data.find(note => note.id === noteId);
    4...

    Update event handler handleSubmit di component:

    src/components/EditNoteForm.js

    1...
    2 const handleSubmit = async (e) => {
    3 e.preventDefault();
    4
    5 try {
    6 const actionResult = await dispatch(updateExistingNote(state));
    7 const result = unwrapResult(actionResult);
    8 if(result) {
    9 setIsSuccess(true);
    10 } else {
    11 setIsSuccess(false);
    12 }
    13 } catch (err) {
    14 console.error("Terjadi kesalahan: ", err);
    15 setIsSuccess(false);
    16 } finally {
    17 dispatch(statusReset());
    18 }
    19 };
    20...

    Delete

    Buat middleware yang bertugas untuk mengirim request delete ke API server:

    src/features/notes/notesSlice.js

    1...
    2export const deleteNote = createAsyncThunk(
    3 "notes/deleteNote",
    4 async(currentNote) => {
    5 const requestOptions = {
    6 method: 'DELETE',
    7 headers: { 'Content-Type': 'application/json' }
    8 };
    9
    10 const response = await fetch(`${process.env.REACT_APP_API_URL}/note/${currentNote._id}`, requestOptions);
    11 if (response.ok) {
    12 return currentNote;
    13 }
    14 }
    15)
    16...

    Tambahkan reducer di field extraReducer:

    1...
    2 [deleteNote.pending]: (state, action) => {
    3 state.status = "loading";
    4 },
    5 [deleteNote.fulfilled]: (state, action) => {
    6 state.status = "succeeded";
    7 const { _id } = action.payload;
    8 const updatedNotes = state.data.filter(note => note._id === _id);
    9 state.data = updatedNotes;
    10 },
    11 [deleteNote.rejected]: (state, action) => {
    12 state.status = "failed";
    13 state.error = action.error.message;
    14 },
    15...

    Update event handler handleDeleteNote:

    src/components/EditNoteForm.js

    1...
    2 const handleDeleteNote = async (e) => {
    3 e.preventDefault();
    4
    5 try {
    6 const actionResult = await dispatch(deleteNote(state));
    7 const result = unwrapResult(actionResult);
    8 if(result) {
    9 setIsSuccess(true);
    10 } else {
    11 setIsSuccess(false);
    12 }
    13 } catch (err) {
    14 console.error("Terjadi kesalahan: ", err);
    15 setIsSuccess(false);
    16 } finally {
    17 dispatch(statusReset());
    18 history.push("/");
    19 }
    20 };
    21...

    Final Code

    Versi akhir dari code cukuplah panjang, kamu bisa lihat di repository dinotes-client pada branch redux.

    Testing

    Kamu bisa jalankan perintah yarn start pada setiap project (client & api) untuk melihat apakah aplikasi DinoTes tetap berjalan dengan baik setelah pengelolaan state diubah dari internal state management (props & state) ke Redux.


    Seperti yang sudah disebutkan sebelumnya, code dari aplikasi DinoTes sekarang menjadi lebih kompleks. Dimana untuk update satu state saja banyak proses yang harus dilalui yaitu event handler -> dispatch action -> reducer -> update state.

    Sesuai rekomendasi dari Redux, gunakan Redux jika:

    • Banyak data yang berubah dari waktu ke waktu
    • Pengelolaan state harus dilakukan di satu tempat (Single source of truth)
    • Mengelola state di top-level component sudah tidak lagi relevan

    Intinya jika tanpa Redux tidak ada masalah, maka tidak perlu gunakan Redux