You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

提交表单后如何触发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

解决方案

问题根源

  1. 全局EntryList是初始化时读取的静态值,添加新条目时未实时更新,导致loader返回旧数据
  2. loader函数未重新从IndexedDB读取最新数据,直接返回旧变量
  3. 表单提交后未触发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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 17:55:01