提交表单后如何触发React Router loader函数更新Entries组件?
问题描述
我参考Journalisticapp.com开发了一个基础日记应用,用于学习React与React Router。Entries组件通过/journal索引路由的loader函数加载日记条目列表,新条目通过React Router表单提交至/entry/add的action函数添加。当前提交表单后需刷新整页才能更新条目,请问如何实现仅更新Entries组件而无需重载页面?
现有代码
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
解决方案
问题根源
- 全局
EntryList是初始化时读取的静态值,添加新条目时未实时更新,导致loader返回旧数据 - loader函数未重新从IndexedDB读取最新数据,直接返回旧变量
- 表单提交后未触发loader重新加载,组件无法感知数据变化
修改步骤
1. 修正数据操作函数,确保数据一致性
删除全局EntryList依赖,每次操作都从DB读取最新数据:
// 替换原有addEntry、removeEntry、editEntry函数 const addEntry = async (entry = { id, content }) => { const currentEntries = await journalDB.getItem('entries') || []; const newEntries = [...currentEntries, entry]; await journalDB.setItem('entries', newEntries); console.log(`entry ${entry.id} saved`); return newEntries; }; const removeEntry = async (oldEntry = {}) => { const currentEntries = await journalDB.getItem('entries') || []; const filteredEntries = currentEntries.filter(entry => entry.id !== oldEntry.id); await journalDB.setItem('entries', filteredEntries); console.log(`entry ${oldEntry.id} removed`); return filteredEntries; }; const editEntry = async (originEntry = { id }, update = { content }) => { const currentEntries = await journalDB.getItem('entries') || []; const newEntries = currentEntries.map(entry => entry.id === originEntry.id ? { ...entry, ...update } : entry ); await journalDB.setItem('entries', newEntries); console.log(`entry ${originEntry.id} updated`); return newEntries; };
2. 更新路由的loader和action,触发无刷新更新
让loader每次都读取最新数据,action提交后通过redirect触发loader重载:
// 替换原有router配置 const router = createBrowserRouter( createRoutesFromElements( <Route path='/' element={<RootLayout />}> <Route id='journal' path='journal' element={<JournalLayout />}> <Route index id='entries' element={<Entries />} loader={async () => { // 每次加载都从DB取最新数据 return await journalDB.getItem('entries') || []; }} /> <Route path='entry/add' action={async ({ params, request }) => { const req = await request.formData(); const currentEntries = await journalDB.getItem('entries') || []; const entry = { id: currentEntries.length + 1, content: req.get('content') }; await addEntry(entry); // 重定向回/journal,触发loader重新加载 return redirect('/journal'); }} /> </Route> </Route> ) );
3. 优化Entries组件的key值
避免使用index作为key,改用条目id确保渲染稳定性:
// 修改Entries.jsx const Entries = () => { const entryList = useLoaderData(); return ( <div className={style.class}> {entryList.map(({ id, content }) => ( <div key={id}>{id}: {content}</div> ))} </div> ); };
4. 可选:优化表单提交体验
利用fetcher状态添加提交中提示:
// 修改JournalLayout.jsx const JournalLayout = () => { const fetcher = useFetcher(); const isSubmitting = fetcher.state === 'submitting'; return ( <div> <div className="journal"> <div>Journal</div> <Outlet /> <div> <fetcher.Form method='post' action='entry/add'> <textarea rows={10} cols={30} name='content' disabled={isSubmitting} /> <input type="submit" value={isSubmitting ? '提交中...' : '添加条目'} disabled={isSubmitting} /> </fetcher.Form> </div> </div> </div> ); };
核心原理
React Router的loader函数会在路由初始化、redirect或调用reload时重新执行,返回的新数据会自动更新使用useLoaderData的组件。通过每次操作读取最新DB数据+提交后redirect触发loader重载,就能实现Entries组件的无刷新更新。
内容的提问来源于stack exchange,提问作者Ericmyerdev
相关产品推荐
相关产品推荐

