perf(web): eliminate redundant fetch when opening inline memo editor

This commit is contained in:
Steven 2026-02-12 23:02:25 +08:00
parent 69485eecb3
commit aeb1e5fe40
5 changed files with 43 additions and 88 deletions

View File

@ -1,69 +1,40 @@
import { useQueryClient } from "@tanstack/react-query";
import { useEffect, useRef } from "react";
import { memoKeys } from "@/hooks/useMemoQueries";
import type { Visibility } from "@/types/proto/api/v1/memo_service_pb";
import type { Memo, Visibility } from "@/types/proto/api/v1/memo_service_pb";
import type { EditorRefActions } from "../Editor";
import { cacheService, memoService } from "../services";
import { useEditorContext } from "../state";
export const useMemoInit = (
editorRef: React.RefObject<EditorRefActions | null>,
memoName: string | undefined,
cacheKey: string | undefined,
username: string,
autoFocus?: boolean,
defaultVisibility?: Visibility,
) => {
interface UseMemoInitOptions {
editorRef: React.RefObject<EditorRefActions | null>;
memo?: Memo;
cacheKey?: string;
username: string;
autoFocus?: boolean;
defaultVisibility?: Visibility;
}
export const useMemoInit = ({ editorRef, memo, cacheKey, username, autoFocus, defaultVisibility }: UseMemoInitOptions) => {
const { actions, dispatch } = useEditorContext();
const queryClient = useQueryClient();
const initializedRef = useRef(false);
useEffect(() => {
if (initializedRef.current) return;
initializedRef.current = true;
const init = async () => {
dispatch(actions.setLoading("loading", true));
try {
if (memoName) {
// Force refetch from server to prevent stale data issues
// See: https://github.com/usememos/memos/issues/5470
await queryClient.invalidateQueries({ queryKey: memoKeys.detail(memoName) });
// Load existing memo
const loadedState = await memoService.load(memoName);
dispatch(
actions.initMemo({
content: loadedState.content,
metadata: loadedState.metadata,
timestamps: loadedState.timestamps,
}),
);
} else {
// Load from cache for new memo
const cachedContent = cacheService.load(cacheService.key(username, cacheKey));
if (cachedContent) {
dispatch(actions.updateContent(cachedContent));
}
// Apply default visibility for new memos
if (defaultVisibility !== undefined) {
dispatch(actions.setMetadata({ visibility: defaultVisibility }));
}
}
} catch (error) {
console.error("Failed to initialize editor:", error);
} finally {
dispatch(actions.setLoading("loading", false));
if (autoFocus) {
setTimeout(() => {
editorRef.current?.focus();
}, 100);
}
if (memo) {
dispatch(actions.initMemo(memoService.fromMemo(memo)));
} else {
const cachedContent = cacheService.load(cacheService.key(username, cacheKey));
if (cachedContent) {
dispatch(actions.updateContent(cachedContent));
}
};
if (defaultVisibility !== undefined) {
dispatch(actions.setMetadata({ visibility: defaultVisibility }));
}
}
init();
}, [memoName, cacheKey, username, autoFocus, defaultVisibility, actions, dispatch, editorRef, queryClient]);
if (autoFocus) {
setTimeout(() => editorRef.current?.focus(), 100);
}
}, [memo, cacheKey, username, autoFocus, defaultVisibility, actions, dispatch, editorRef]);
};

View File

@ -17,29 +17,16 @@ import { cacheService, errorService, memoService, validationService } from "./se
import { EditorProvider, useEditorContext } from "./state";
import type { MemoEditorProps } from "./types";
const MemoEditor = (props: MemoEditorProps) => {
const { className, cacheKey, memoName, parentMemoName, autoFocus, placeholder, onConfirm, onCancel } = props;
return (
<EditorProvider>
<MemoEditorImpl
className={className}
cacheKey={cacheKey}
memoName={memoName}
parentMemoName={parentMemoName}
autoFocus={autoFocus}
placeholder={placeholder}
onConfirm={onConfirm}
onCancel={onCancel}
/>
</EditorProvider>
);
};
const MemoEditor = (props: MemoEditorProps) => (
<EditorProvider>
<MemoEditorImpl {...props} />
</EditorProvider>
);
const MemoEditorImpl: React.FC<MemoEditorProps> = ({
className,
cacheKey,
memoName,
memo,
parentMemoName,
autoFocus,
placeholder,
@ -53,10 +40,12 @@ const MemoEditorImpl: React.FC<MemoEditorProps> = ({
const { state, actions, dispatch } = useEditorContext();
const { userGeneralSetting } = useAuth();
const memoName = memo?.name;
// Get default visibility from user settings
const defaultVisibility = userGeneralSetting?.memoVisibility ? convertVisibilityFromString(userGeneralSetting.memoVisibility) : undefined;
useMemoInit(editorRef, memoName, cacheKey, currentUser?.name ?? "", autoFocus, defaultVisibility);
useMemoInit({ editorRef, memo, cacheKey, username: currentUser?.name ?? "", autoFocus, defaultVisibility });
// Auto-save content to localStorage
useAutoSave(state.content, currentUser?.name ?? "", cacheKey);

View File

@ -122,9 +122,8 @@ export const memoService = {
return { memoName: memo.name, hasChanges: true };
},
async load(memoName: string): Promise<EditorState> {
const memo = await memoServiceClient.getMemo({ name: memoName });
/** Build editor state from an already-loaded Memo entity (no network request). */
fromMemo(memo: Memo): EditorState {
return {
content: memo.content,
metadata: {
@ -135,11 +134,7 @@ export const memoService = {
},
ui: {
isFocusMode: false,
isLoading: {
saving: false,
uploading: false,
loading: false,
},
isLoading: { saving: false, uploading: false, loading: false },
isDragging: false,
isComposing: false,
},

View File

@ -8,7 +8,8 @@ export interface MemoEditorProps {
className?: string;
cacheKey?: string;
placeholder?: string;
memoName?: string;
/** Existing memo to edit. When provided, the editor initializes from it without fetching. */
memo?: Memo;
parentMemoName?: string;
autoFocus?: boolean;
onConfirm?: (memoName: string) => void;

View File

@ -31,8 +31,7 @@ const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
const { previewState, openPreview, setPreviewOpen } = useImagePreview();
const { unpinMemo } = useMemoActions(memoData, isArchived);
const handleEditorConfirm = () => setShowEditor(false);
const handleEditorCancel = () => setShowEditor(false);
const closeEditor = () => setShowEditor(false);
const openEditor = () => setShowEditor(true);
const { handleGotoMemoDetailPage, handleMemoContentClick, handleMemoContentDoubleClick } = useMemoHandlers({
@ -63,9 +62,9 @@ const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
autoFocus
className="mb-2"
cacheKey={`inline-memo-editor-${memoData.name}`}
memoName={memoData.name}
onConfirm={handleEditorConfirm}
onCancel={handleEditorCancel}
memo={memoData}
onConfirm={closeEditor}
onCancel={closeEditor}
/>
);
}