I’m working on a basic journaling app based using some inspiration from Journalisticapp.com. I’m using this as a way to learn/practice React and React Router.
The Entries
component should contain a list of journal entries that are pulled with a loader function on the /journal
index route. Each entry is added via React Router form element, then submitted to /entry/add
with an action function.
After submitting the form I want the journal entries to update, but it requires a full page reload. How can I get only the Entries
component to reload after submitting the form without having to reload the page?
Routes.jsx
import React from 'react';
import { createRoot } from 'react-dom/client'
import { RouterProvider, createBrowserRouter, createRoutesFromElements, Route, Routes } from "react-router-dom";
import localforage from 'localforage';
import RootLayout from './layouts/RootLayout'
import JournalLayout from './layouts/JournalLayout';
import Entries from './components/Entries';
const journalDB = localforage.createInstance({
driver: localforage.INDEXEDDB,
storeName: 'journalDB',
})
const EntryList = await journalDB.getItem('entries') || []
const addEntry = (entry = { id, content }) => {
journalDB.setItem('entries', [...EntryList, entry]).then((entries) => {
console.log(`entry ${entry.id} saved`)
}).catch((err) => console.log(err))
}
const removeEntry = (oldEntry = {}) => {
let filteredEntries = Entries.filter(entry => entry !== oldEntry)
journalDB.setItem('entries', filteredEntries).then((entries) => {
console.log(`entry ${oldEntry.id} removed`)
}).catch((err) => console.log(err))
}
const editEntry = (originEntry = { id }, update = { content }) => {
let newEntry = { ...originEntry, ...update }
removeEntry(originEntry)
addEntry(newEntry)
}
const router = createBrowserRouter(
createRoutesFromElements(
<Route
path='/'
element={<RootLayout />}>
<Route
id='journal'
path='journal'
element={<JournalLayout />}>
<Route
index
id='entries'
element={<Entries />}
loader={async () => { return EntryList }} />
<Route
path='entry/add'
action={async ({ params, request }) => {
const req = await request.formData()
let entry = {
id: EntryList.length + 1,
content: req.get('content')
}
addEntry(entry)
return EntryList
}} />
</Route>
</Route>
)
)
createRoot(document.querySelector('#root')).render(
<RouterProvider router={router} />
);
JournalLayout.jsx
import React, { useEffect } from 'react'
import { Outlet, Form, useFetcher } from 'react-router-dom'
const JournalLayout = () => {
const fetcher = useFetcher()
return (
<div>
<div className="journal">
<div>Journal</div>
<Outlet />
<div>
<fetcher.Form method='put' action='entry/add' >
<textarea rows={10} cols={30} type='text' name='content' />
<input type="submit" />
</fetcher.Form>
</div>
</div>
</div >
)
}
export default JournalLayout
Entries.jsx
import React, { useState, useEffect } from 'react'
import style from './Entries.scss'
import { useLoaderData } from 'react-router-dom'
const Entries = () => {
const EntryList = useLoaderData()
return (
<div className={style.class}>{EntryList.map(({ id, content }, idx) => <div key={idx}>{id}: {content}</div>)}</div>
)
}
export default Entries
2
Answers
Why your problem is happening
After looking over your code a few times, the problem lies in how you initialize the
EntryList
array. You make one call to the database to fetch results and nothing new ever happens to the array. The route also has theloader
parameter passing back the array and not the results of a new call to the database.Thankfully this is a very easy fix, and it can be done in multiple ways.
First option (off the top of my head)
Instead of passing the
EntryList
array in the route’sloader
, pass the results of a new call to the database (the call should fetch all the entries).Problems here is performance. Could be slow, depending on db and how the fetching function is implemented.
Second option (more thought out, less calls to the database, so faster UI)
When adding new entries, make sure you keep the
EntryList
up-to-date. This may be a solution you can use without needing to add some React Context or extra react hook callback code.If you choose to update the
EntryList
array every time you ADD/DELETE/UPDATE, you need to add the code to all functions that preform crud operations to the database.addEntry
,editEntry
, andremoveEntry
need to be updated and tested.See the code snippet below:
See your full file with the (some) changes. I also have prettier format all my files, so I prettified your react code. Sorry!
From what I can tell it seems that
EntryList
is computed only once when the app is loaded and theRoutes.jsx
file is processed. The loader function is simply returning the initially computed value.The loader should call the DB/backend at runtime.