@@ -46,6 +46,8 @@ export function useAutosave({
4646
4747 const isDirty = content !== savedContent
4848 const savingStartRef = useRef ( 0 )
49+ const inFlightRef = useRef < Promise < void > | null > ( null )
50+ const unmountedRef = useRef ( false )
4951 const MIN_SAVING_DISPLAY_MS = 600
5052
5153 const save = useCallback ( async ( ) => {
@@ -58,25 +60,33 @@ export function useAutosave({
5860 }
5961 savingRef . current = true
6062 savingStartRef . current = Date . now ( )
61- setSaveStatus ( 'saving' )
62- let nextStatus : SaveStatus = 'saved'
63- try {
64- await onSaveRef . current ( )
65- } catch {
66- nextStatus = 'error'
67- } finally {
68- const elapsed = Date . now ( ) - savingStartRef . current
69- const remaining = Math . max ( 0 , MIN_SAVING_DISPLAY_MS - elapsed )
70- displayTimerRef . current = setTimeout ( ( ) => {
71- setSaveStatus ( nextStatus )
72- clearTimeout ( idleTimerRef . current )
73- idleTimerRef . current = setTimeout ( ( ) => setSaveStatus ( 'idle' ) , 2000 )
74- savingRef . current = false
75- if ( nextStatus !== 'error' && contentRef . current !== savedContentRef . current ) {
76- save ( )
63+ if ( ! unmountedRef . current ) setSaveStatus ( 'saving' )
64+ const run = ( async ( ) => {
65+ let nextStatus : SaveStatus = 'saved'
66+ try {
67+ await onSaveRef . current ( )
68+ } catch {
69+ nextStatus = 'error'
70+ } finally {
71+ if ( unmountedRef . current ) {
72+ savingRef . current = false
73+ } else {
74+ const elapsed = Date . now ( ) - savingStartRef . current
75+ const remaining = Math . max ( 0 , MIN_SAVING_DISPLAY_MS - elapsed )
76+ displayTimerRef . current = setTimeout ( ( ) => {
77+ setSaveStatus ( nextStatus )
78+ clearTimeout ( idleTimerRef . current )
79+ idleTimerRef . current = setTimeout ( ( ) => setSaveStatus ( 'idle' ) , 2000 )
80+ savingRef . current = false
81+ if ( nextStatus !== 'error' && contentRef . current !== savedContentRef . current ) {
82+ save ( )
83+ }
84+ } , remaining )
7785 }
78- } , remaining )
79- }
86+ }
87+ } ) ( )
88+ inFlightRef . current = run
89+ await run
8090 } , [ ] )
8191
8292 useEffect ( ( ) => {
@@ -88,15 +98,20 @@ export function useAutosave({
8898
8999 useEffect ( ( ) => {
90100 return ( ) => {
101+ unmountedRef . current = true
91102 clearTimeout ( timerRef . current )
92103 clearTimeout ( idleTimerRef . current )
93104 clearTimeout ( displayTimerRef . current )
94- // Flush the latest content on unmount even if a save is mid-flight: that in-flight save
95- // captured an older snapshot, so skipping here would terminally drop any edit typed since.
96- // The duplicate PUT is idempotent.
97- if ( enabledRef . current && contentRef . current !== savedContentRef . current ) {
98- onSaveRef . current ( ) . catch ( ( ) => { } )
99- }
105+ if ( ! enabledRef . current || contentRef . current === savedContentRef . current ) return
106+ // Flush the latest content on unmount, but chain it AFTER any in-flight save rather than
107+ // firing a concurrent PUT: the in-flight save captured an older snapshot, so writing the
108+ // latest sequentially (last) prevents an out-of-order completion from clobbering it.
109+ void ( async ( ) => {
110+ await inFlightRef . current
111+ if ( contentRef . current !== savedContentRef . current ) {
112+ await onSaveRef . current ( ) . catch ( ( ) => { } )
113+ }
114+ } ) ( )
100115 }
101116 } , [ ] )
102117
0 commit comments