From d9ad94e49cf0ad53f5e429acdc54741844da2f59 Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Thu, 23 Oct 2025 10:53:22 +0530 Subject: [PATCH 01/19] adding fix for TextInput --- .../TextInput/WindowsTextInputComponentView.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp index a767534b8ad..04f59d9121a 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include #include @@ -537,7 +539,10 @@ void WindowsTextInputComponentView::HandleCommand( std::optional text; winrt::Microsoft::ReactNative::ReadArgs(args.CommandArgs(), eventCount, text, begin, end); - if (eventCount >= m_nativeEventCount) { + // Allow text updates that are very close to the current native event count + // This handles the case where JavaScript immediately calls setValue during onSubmitEditing + // In that case, eventCount might be one less than m_nativeEventCount due to timing + if (eventCount >= m_nativeEventCount - 1) { m_comingFromJS = true; { if (text.has_value()) { @@ -957,6 +962,13 @@ void WindowsTextInputComponentView::OnCharacterReceived( if (m_clearTextOnSubmit) { // clear text from RichEdit m_textServices->TxSetText(L""); + // Also update the state to reflect the cleared text + // This ensures consistency between native and JS state + auto data = m_state->getData(); + data.attributedStringBox = facebook::react::AttributedStringBox{facebook::react::AttributedString{}}; + data.mostRecentEventCount = m_nativeEventCount; + + m_state->updateState(std::move(data)); } return; } From 0e83625899d4c7ff20019a3eb1fedeca250bc0c9 Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Thu, 23 Oct 2025 10:53:54 +0530 Subject: [PATCH 02/19] Change files --- ...ative-windows-daac39ef-4292-4f66-81ae-708b1c0f6676.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/react-native-windows-daac39ef-4292-4f66-81ae-708b1c0f6676.json diff --git a/change/react-native-windows-daac39ef-4292-4f66-81ae-708b1c0f6676.json b/change/react-native-windows-daac39ef-4292-4f66-81ae-708b1c0f6676.json new file mode 100644 index 00000000000..48c0583f2b9 --- /dev/null +++ b/change/react-native-windows-daac39ef-4292-4f66-81ae-708b1c0f6676.json @@ -0,0 +1,7 @@ +{ + "comment": "adding fix for TextInput", + "type": "prerelease", + "packageName": "react-native-windows", + "email": "protikbiswas100@microsoft.com", + "dependentChangeType": "patch" +} From da2a4bf15232732699783e78a924e2bc54dd4bf9 Mon Sep 17 00:00:00 2001 From: Protik Biswas <219775028+protikbiswas100@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:55:43 +0530 Subject: [PATCH 03/19] Update change/react-native-windows-daac39ef-4292-4f66-81ae-708b1c0f6676.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ...act-native-windows-daac39ef-4292-4f66-81ae-708b1c0f6676.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/change/react-native-windows-daac39ef-4292-4f66-81ae-708b1c0f6676.json b/change/react-native-windows-daac39ef-4292-4f66-81ae-708b1c0f6676.json index 48c0583f2b9..e0c0d3e7754 100644 --- a/change/react-native-windows-daac39ef-4292-4f66-81ae-708b1c0f6676.json +++ b/change/react-native-windows-daac39ef-4292-4f66-81ae-708b1c0f6676.json @@ -1,5 +1,5 @@ { - "comment": "adding fix for TextInput", + "comment": "Fix TextInput clearTextOnSubmit-style behavior regression in Fabric architecture", "type": "prerelease", "packageName": "react-native-windows", "email": "protikbiswas100@microsoft.com", From 13473eda91dea2ecd362021c82fb40e365c9cbfb Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Mon, 27 Oct 2025 12:55:20 +0530 Subject: [PATCH 04/19] changed logic for native and js side event count --- .../TextInput/TextInputExample.windows.js | 32 +++++++++++++++ .../test/TextInputComponentTest.test.ts | 41 +++++++++++++++++++ .../WindowsTextInputComponentView.cpp | 11 +++-- 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/packages/@react-native-windows/tester/src/js/examples/TextInput/TextInputExample.windows.js b/packages/@react-native-windows/tester/src/js/examples/TextInput/TextInputExample.windows.js index 5825a357099..6faa3544443 100644 --- a/packages/@react-native-windows/tester/src/js/examples/TextInput/TextInputExample.windows.js +++ b/packages/@react-native-windows/tester/src/js/examples/TextInput/TextInputExample.windows.js @@ -796,6 +796,38 @@ const examples: Array = [ ); }, }, + { + title: 'Manual clear text on submit (setValue in onSubmitEditing)', + render: function () { + function ManualClearExample() { + const [value, setValue] = React.useState(''); + const submitValue = () => { + // Simulate the regression scenario: manual setValue('') during onSubmitEditing + if (value !== '') { + setValue(''); + } + }; + return ( + + + Manual clear using setValue('') in onSubmitEditing callback: + + + Current value: "{value}" + + ); + } + return ; + }, + }, { title: 'Stop propagation sample', render: function (): React.Node { diff --git a/packages/e2e-test-app-fabric/test/TextInputComponentTest.test.ts b/packages/e2e-test-app-fabric/test/TextInputComponentTest.test.ts index 1894a4bca81..2c606ff540e 100644 --- a/packages/e2e-test-app-fabric/test/TextInputComponentTest.test.ts +++ b/packages/e2e-test-app-fabric/test/TextInputComponentTest.test.ts @@ -414,6 +414,47 @@ describe('TextInput Tests', () => { const dump = await dumpVisualTree('textinput-clear-on-submit-4'); expect(dump).toMatchSnapshot(); }); + test('TextInputs can manually clear text on submit using setValue in onSubmitEditing', async () => { + const component = await app.findElementByTestID( + 'textinput-manual-clear-on-submit', + ); + await component.waitForDisplayed({timeout: 5000}); + + // Enter text in the input + await app.waitUntil( + async () => { + await component.setValue('Hello World'); + return (await component.getText()) === 'Hello World'; + }, + { + interval: 1500, + timeout: 5000, + timeoutMsg: `Unable to enter correct text.`, + }, + ); + + // Verify text was entered + expect(await component.getText()).toBe('Hello World'); + + // Press Enter to trigger onSubmitEditing which should clear the text + await app.waitUntil( + async () => { + await component.setValue('\uE007'); // Enter key + return (await component.getText()) === ''; + }, + { + interval: 1500, + timeout: 5000, + timeoutMsg: `Text should be cleared after pressing Enter (manual setValue('') in onSubmitEditing)`, + }, + ); + + // Verify text was cleared + expect(await component.getText()).toBe(''); + + const dump = await dumpVisualTree('textinput-manual-clear-on-submit'); + expect(dump).toMatchSnapshot(); + }); test('TextInputs can keep text on focus', async () => { const componentFocusFalse = await app.findElementByTestID( 'clear-text-on-focus-false', diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp index 04f59d9121a..bfcb0320683 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp @@ -539,10 +539,13 @@ void WindowsTextInputComponentView::HandleCommand( std::optional text; winrt::Microsoft::ReactNative::ReadArgs(args.CommandArgs(), eventCount, text, begin, end); - // Allow text updates that are very close to the current native event count - // This handles the case where JavaScript immediately calls setValue during onSubmitEditing - // In that case, eventCount might be one less than m_nativeEventCount due to timing - if (eventCount >= m_nativeEventCount - 1) { + // Accept text updates that match current event count, or clear text operations + // that are one event behind (to handle immediate setValue('') during onSubmitEditing) + bool isEmptyTextUpdate = text.has_value() && text.value().empty(); + bool isValidEventCount = eventCount >= m_nativeEventCount; + bool isRecentClearText = isEmptyTextUpdate && (eventCount >= m_nativeEventCount - 1); + + if (isValidEventCount || isRecentClearText) { m_comingFromJS = true; { if (text.has_value()) { From 8771b9ee46524743c7bed30e04423e5a3456eb69 Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Thu, 30 Oct 2025 12:04:28 +0530 Subject: [PATCH 05/19] adding formatting changes --- .../Samples/textInputSubmitIssue.tsx | 192 ++++++++++++++++++ .../WindowsTextInputComponentView.cpp | 2 +- 2 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 packages/playground/Samples/textInputSubmitIssue.tsx diff --git a/packages/playground/Samples/textInputSubmitIssue.tsx b/packages/playground/Samples/textInputSubmitIssue.tsx new file mode 100644 index 00000000000..c4b6a25f062 --- /dev/null +++ b/packages/playground/Samples/textInputSubmitIssue.tsx @@ -0,0 +1,192 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * @format + */ + +import React from 'react'; +import { + StyleSheet, + Text, + TextInput, + View, + ScrollView, + Alert, + AppRegistry, +} from 'react-native'; + +interface ChatEntryProps { + submit: (value: string) => void; +} + +function ChatEntry({submit}: ChatEntryProps) { + const [value, setValue] = React.useState(''); + + const submitValue = () => { + // If the user hits submit but the text is empty, don't carry that forward + if (value !== '') { + submit(value); + // Reset to a blank prompt - THIS IS THE BUG: setValue('') doesn't work in Fabric during onSubmitEditing + setValue(''); + } + }; + + return ( + + + Manual setValue on submit (broken in Fabric): + + setValue(newValue)} + onSubmitEditing={submitValue} + value={value} + blurOnSubmit={false} + /> + + ); +} + +function WorkingChatEntry({submit}: ChatEntryProps) { + const [value, setValue] = React.useState(''); + + const submitValue = () => { + if (value !== '') { + submit(value); + // Manual clear - this is the workaround that should work + setValue(''); + } + }; + + return ( + + Workaround - manual clear after submit: + setValue(newValue)} + onSubmitEditing={submitValue} + value={value} + blurOnSubmit={false} + /> + + ); +} + +export default function TextInputSubmitIssue() { + const [submissions, setSubmissions] = React.useState([]); + + const handleSubmit = (text: string) => { + setSubmissions(prev => [...prev, text]); + Alert.alert('Submitted', `Text: "${text}"`); + }; + + return ( + + + TextInput Submit Issue Reproduction + + This demonstrates the bug in GitHub issue #15168 where setValue('') + doesn't work during onSubmitEditing in Fabric architecture. + {'\n\n'} + Note: In the actual Windows implementation, this would use + clearTextOnSubmit and submitKeyEvents props. This simplified version + shows the core issue with programmatic value updates during submit. + + + + + + + + Submitted texts: + {submissions.map((text, index) => ( + + {index + 1}. "{text}" + + ))} + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#f5f5f5', + }, + header: { + padding: 20, + backgroundColor: '#ffffff', + borderBottomWidth: 1, + borderBottomColor: '#e0e0e0', + }, + title: { + fontSize: 24, + fontWeight: 'bold', + color: '#333333', + marginBottom: 10, + }, + description: { + fontSize: 16, + color: '#666666', + lineHeight: 22, + }, + containerRow: { + margin: 20, + padding: 15, + backgroundColor: '#ffffff', + borderRadius: 8, + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.1, + shadowRadius: 3.84, + elevation: 5, + }, + label: { + fontSize: 16, + fontWeight: '600', + color: '#333333', + marginBottom: 10, + }, + textInput: { + borderWidth: 1, + borderColor: '#cccccc', + borderRadius: 6, + padding: 12, + fontSize: 16, + backgroundColor: '#ffffff', + minHeight: 80, + textAlignVertical: 'top', + }, + submissionsList: { + margin: 20, + padding: 15, + backgroundColor: '#ffffff', + borderRadius: 8, + }, + subtitle: { + fontSize: 18, + fontWeight: '600', + color: '#333333', + marginBottom: 10, + }, + submissionItem: { + fontSize: 14, + color: '#666666', + marginBottom: 5, + paddingLeft: 10, + }, +}); + +AppRegistry.registerComponent('Bootstrap', () => TextInputSubmitIssue); diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp index 27b7d479141..a3200daf43c 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp @@ -548,7 +548,7 @@ void WindowsTextInputComponentView::HandleCommand( bool isEmptyTextUpdate = text.has_value() && text.value().empty(); bool isValidEventCount = eventCount >= m_nativeEventCount; bool isRecentClearText = isEmptyTextUpdate && (eventCount >= m_nativeEventCount - 1); - + if (isValidEventCount || isRecentClearText) { m_comingFromJS = true; { From 66e141d485896d73457bdd745e635a2eb4c46b6c Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Thu, 30 Oct 2025 13:59:53 +0530 Subject: [PATCH 06/19] adding linting fix --- .../src/js/examples/TextInput/TextInputExample.windows.js | 2 +- .../Composition/TextInput/WindowsTextInputComponentView.cpp | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/@react-native-windows/tester/src/js/examples/TextInput/TextInputExample.windows.js b/packages/@react-native-windows/tester/src/js/examples/TextInput/TextInputExample.windows.js index f7510b866ee..67b3cae9f21 100644 --- a/packages/@react-native-windows/tester/src/js/examples/TextInput/TextInputExample.windows.js +++ b/packages/@react-native-windows/tester/src/js/examples/TextInput/TextInputExample.windows.js @@ -762,7 +762,7 @@ const examples: Array = [ }, // [Windows { - title: 'Clear text on submit', + title: 'Clear text on submitting', render: function (): React.Node { return ( diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp index a3200daf43c..ad9a7fb1c53 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp @@ -971,11 +971,6 @@ void WindowsTextInputComponentView::OnCharacterReceived( m_textServices->TxSetText(L""); // Also update the state to reflect the cleared text // This ensures consistency between native and JS state - auto data = m_state->getData(); - data.attributedStringBox = facebook::react::AttributedStringBox{facebook::react::AttributedString{}}; - data.mostRecentEventCount = m_nativeEventCount; - - m_state->updateState(std::move(data)); } return; } From 89c3b49ad657b0343fcd40c707ccc6d3a7dfaecc Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Thu, 30 Oct 2025 14:03:47 +0530 Subject: [PATCH 07/19] Remove packages/playground/Samples/textInputSubmitIssue.tsx --- .../Samples/textInputSubmitIssue.tsx | 192 ------------------ 1 file changed, 192 deletions(-) delete mode 100644 packages/playground/Samples/textInputSubmitIssue.tsx diff --git a/packages/playground/Samples/textInputSubmitIssue.tsx b/packages/playground/Samples/textInputSubmitIssue.tsx deleted file mode 100644 index c4b6a25f062..00000000000 --- a/packages/playground/Samples/textInputSubmitIssue.tsx +++ /dev/null @@ -1,192 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT License. - * @format - */ - -import React from 'react'; -import { - StyleSheet, - Text, - TextInput, - View, - ScrollView, - Alert, - AppRegistry, -} from 'react-native'; - -interface ChatEntryProps { - submit: (value: string) => void; -} - -function ChatEntry({submit}: ChatEntryProps) { - const [value, setValue] = React.useState(''); - - const submitValue = () => { - // If the user hits submit but the text is empty, don't carry that forward - if (value !== '') { - submit(value); - // Reset to a blank prompt - THIS IS THE BUG: setValue('') doesn't work in Fabric during onSubmitEditing - setValue(''); - } - }; - - return ( - - - Manual setValue on submit (broken in Fabric): - - setValue(newValue)} - onSubmitEditing={submitValue} - value={value} - blurOnSubmit={false} - /> - - ); -} - -function WorkingChatEntry({submit}: ChatEntryProps) { - const [value, setValue] = React.useState(''); - - const submitValue = () => { - if (value !== '') { - submit(value); - // Manual clear - this is the workaround that should work - setValue(''); - } - }; - - return ( - - Workaround - manual clear after submit: - setValue(newValue)} - onSubmitEditing={submitValue} - value={value} - blurOnSubmit={false} - /> - - ); -} - -export default function TextInputSubmitIssue() { - const [submissions, setSubmissions] = React.useState([]); - - const handleSubmit = (text: string) => { - setSubmissions(prev => [...prev, text]); - Alert.alert('Submitted', `Text: "${text}"`); - }; - - return ( - - - TextInput Submit Issue Reproduction - - This demonstrates the bug in GitHub issue #15168 where setValue('') - doesn't work during onSubmitEditing in Fabric architecture. - {'\n\n'} - Note: In the actual Windows implementation, this would use - clearTextOnSubmit and submitKeyEvents props. This simplified version - shows the core issue with programmatic value updates during submit. - - - - - - - - Submitted texts: - {submissions.map((text, index) => ( - - {index + 1}. "{text}" - - ))} - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#f5f5f5', - }, - header: { - padding: 20, - backgroundColor: '#ffffff', - borderBottomWidth: 1, - borderBottomColor: '#e0e0e0', - }, - title: { - fontSize: 24, - fontWeight: 'bold', - color: '#333333', - marginBottom: 10, - }, - description: { - fontSize: 16, - color: '#666666', - lineHeight: 22, - }, - containerRow: { - margin: 20, - padding: 15, - backgroundColor: '#ffffff', - borderRadius: 8, - shadowColor: '#000', - shadowOffset: { - width: 0, - height: 2, - }, - shadowOpacity: 0.1, - shadowRadius: 3.84, - elevation: 5, - }, - label: { - fontSize: 16, - fontWeight: '600', - color: '#333333', - marginBottom: 10, - }, - textInput: { - borderWidth: 1, - borderColor: '#cccccc', - borderRadius: 6, - padding: 12, - fontSize: 16, - backgroundColor: '#ffffff', - minHeight: 80, - textAlignVertical: 'top', - }, - submissionsList: { - margin: 20, - padding: 15, - backgroundColor: '#ffffff', - borderRadius: 8, - }, - subtitle: { - fontSize: 18, - fontWeight: '600', - color: '#333333', - marginBottom: 10, - }, - submissionItem: { - fontSize: 14, - color: '#666666', - marginBottom: 5, - paddingLeft: 10, - }, -}); - -AppRegistry.registerComponent('Bootstrap', () => TextInputSubmitIssue); From d00e1c4ad8a9f253f3001dacb0332e5cd0720e45 Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Thu, 30 Oct 2025 14:32:49 +0530 Subject: [PATCH 08/19] Removing additional imports --- .../Composition/TextInput/WindowsTextInputComponentView.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp index ad9a7fb1c53..1c5f817ec77 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp @@ -11,8 +11,6 @@ #include #include #include -#include -#include #include #include #include @@ -969,8 +967,6 @@ void WindowsTextInputComponentView::OnCharacterReceived( if (m_clearTextOnSubmit) { // clear text from RichEdit m_textServices->TxSetText(L""); - // Also update the state to reflect the cleared text - // This ensures consistency between native and JS state } return; } From fcd447b79084c8e4171527cde2e66a0783b0ccb8 Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Mon, 3 Nov 2025 12:25:56 +0530 Subject: [PATCH 09/19] changing logic for synchronization --- .../TextInput/WindowsTextInputComponentView.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp index 1c5f817ec77..8807f69daea 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp @@ -541,13 +541,9 @@ void WindowsTextInputComponentView::HandleCommand( std::optional text; winrt::Microsoft::ReactNative::ReadArgs(args.CommandArgs(), eventCount, text, begin, end); - // Accept text updates that match current event count, or clear text operations - // that are one event behind (to handle immediate setValue('') during onSubmitEditing) - bool isEmptyTextUpdate = text.has_value() && text.value().empty(); - bool isValidEventCount = eventCount >= m_nativeEventCount; - bool isRecentClearText = isEmptyTextUpdate && (eventCount >= m_nativeEventCount - 1); - - if (isValidEventCount || isRecentClearText) { + // Only accept text updates that match the current native event count + // This prevents race conditions and maintains proper state synchronization + if (eventCount >= m_nativeEventCount) { m_comingFromJS = true; { if (text.has_value()) { @@ -960,8 +956,12 @@ void WindowsTextInputComponentView::OnCharacterReceived( auto emitter = std::static_pointer_cast(m_eventEmitter); facebook::react::WindowsTextInputEventEmitter::OnSubmitEditing onSubmitEditingArgs; onSubmitEditingArgs.text = GetTextFromRichEdit(); - onSubmitEditingArgs.eventCount = ++m_nativeEventCount; + // Don't increment event count yet - let JS respond with current count first + onSubmitEditingArgs.eventCount = m_nativeEventCount; emitter->onSubmitEditing(onSubmitEditingArgs); + + // Increment after emitting to allow JS to respond with matching count + ++m_nativeEventCount; } if (m_clearTextOnSubmit) { From 528c58bd88a9d1355cd53a2c4ec0a972796473b1 Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Mon, 3 Nov 2025 13:43:29 +0530 Subject: [PATCH 10/19] fixing linting issue --- .../Composition/TextInput/WindowsTextInputComponentView.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp index 8807f69daea..fe85df3b120 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp @@ -542,7 +542,7 @@ void WindowsTextInputComponentView::HandleCommand( winrt::Microsoft::ReactNative::ReadArgs(args.CommandArgs(), eventCount, text, begin, end); // Only accept text updates that match the current native event count - // This prevents race conditions and maintains proper state synchronization + // This prevents race conditions and maintains proper state synchronization if (eventCount >= m_nativeEventCount) { m_comingFromJS = true; { @@ -959,7 +959,7 @@ void WindowsTextInputComponentView::OnCharacterReceived( // Don't increment event count yet - let JS respond with current count first onSubmitEditingArgs.eventCount = m_nativeEventCount; emitter->onSubmitEditing(onSubmitEditingArgs); - + // Increment after emitting to allow JS to respond with matching count ++m_nativeEventCount; } From 8a5fa68db03b7590a84295e28c97e6248057aefb Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Mon, 3 Nov 2025 14:43:24 +0530 Subject: [PATCH 11/19] adding snapshots --- .../TextInputComponentTest.test.ts.snap | 22 +++---- .../__snapshots__/snapshotPages.test.js.snap | 57 +++++++++++++++++-- 2 files changed, 62 insertions(+), 17 deletions(-) diff --git a/packages/e2e-test-app-fabric/test/__snapshots__/TextInputComponentTest.test.ts.snap b/packages/e2e-test-app-fabric/test/__snapshots__/TextInputComponentTest.test.ts.snap index 2c3f866f0ce..2bda46c9a50 100644 --- a/packages/e2e-test-app-fabric/test/__snapshots__/TextInputComponentTest.test.ts.snap +++ b/packages/e2e-test-app-fabric/test/__snapshots__/TextInputComponentTest.test.ts.snap @@ -333,7 +333,7 @@ exports[`TextInput Tests Text have cursorColor 1`] = ` "Visual Tree": { "Comment": "textinput-cursorColor", "Offset": "0, 0, 0", - "Size": "916, 32", + "Size": "916, 33", "Visual Type": "SpriteVisual", "__Children": [ { @@ -1153,7 +1153,7 @@ exports[`TextInput Tests TextInputs can be defined as a set using accessibilityP "Visual Tree": { "Comment": "textinput-set", "Offset": "0, 0, 0", - "Size": "916, 94", + "Size": "916, 93", "Visual Type": "SpriteVisual", "__Children": [ { @@ -1222,12 +1222,12 @@ exports[`TextInput Tests TextInputs can be defined as a set using accessibilityP }, { "Offset": "0, 31, 0", - "Size": "916, 32", + "Size": "916, 33", "Visual Type": "SpriteVisual", "__Children": [ { "Offset": "0, 0, 0", - "Size": "916, 32", + "Size": "916, 33", "Visual Type": "SpriteVisual", "__Children": [ { @@ -1286,12 +1286,12 @@ exports[`TextInput Tests TextInputs can be defined as a set using accessibilityP }, { "Offset": "0, 62, 0", - "Size": "916, 33", + "Size": "916, 32", "Visual Type": "SpriteVisual", "__Children": [ { "Offset": "0, 0, 0", - "Size": "916, 33", + "Size": "916, 32", "Visual Type": "SpriteVisual", "__Children": [ { @@ -2757,7 +2757,7 @@ exports[`TextInput Tests TextInputs can have caretHidden 1`] = ` "Visual Tree": { "Comment": "textinput-carethidden", "Offset": "0, 0, 0", - "Size": "916, 32", + "Size": "916, 33", "Visual Type": "SpriteVisual", "__Children": [ { @@ -4333,7 +4333,7 @@ exports[`TextInput Tests TextInputs can have shadows 1`] = ` "Visual Tree": { "Comment": "textinput-shadow", "Offset": "0, 0, 0", - "Size": "916, 33", + "Size": "916, 32", "Visual Type": "SpriteVisual", "__Children": [ { @@ -6041,17 +6041,17 @@ exports[`TextInput Tests TextInputs which have a searchbox role should also supp "Visual Tree": { "Comment": "textinput-searchbox", "Offset": "0, 0, 0", - "Size": "916, 31", + "Size": "916, 32", "Visual Type": "SpriteVisual", "__Children": [ { "Offset": "0, 0, 0", - "Size": "916, 33", + "Size": "916, 32", "Visual Type": "SpriteVisual", "__Children": [ { "Offset": "0, 0, 0", - "Size": "916, 33", + "Size": "916, 32", "Visual Type": "SpriteVisual", "__Children": [ { diff --git a/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap b/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap index cabc5c16230..dd53f45b53a 100644 --- a/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap +++ b/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap @@ -81312,6 +81312,51 @@ exports[`snapshotAllPages TextInput 43`] = ` `; exports[`snapshotAllPages TextInput 44`] = ` + + + Manual clear using setValue('') in onSubmitEditing callback: + + + + Current value: " + " + + +`; + +exports[`snapshotAllPages TextInput 45`] = ` [ Spell Check Enabled: @@ -81426,7 +81471,7 @@ exports[`snapshotAllPages TextInput 45`] = ` ] `; -exports[`snapshotAllPages TextInput 46`] = ` +exports[`snapshotAllPages TextInput 47`] = ` CaretHidden @@ -81458,7 +81503,7 @@ exports[`snapshotAllPages TextInput 46`] = ` `; -exports[`snapshotAllPages TextInput 47`] = ` +exports[`snapshotAllPages TextInput 48`] = ` Cursorcolor @@ -81490,7 +81535,7 @@ exports[`snapshotAllPages TextInput 47`] = ` `; -exports[`snapshotAllPages TextInput 48`] = ` +exports[`snapshotAllPages TextInput 49`] = ` Shadow @@ -81528,7 +81573,7 @@ exports[`snapshotAllPages TextInput 48`] = ` `; -exports[`snapshotAllPages TextInput 49`] = ` +exports[`snapshotAllPages TextInput 50`] = ` `; -exports[`snapshotAllPages TextInput 50`] = ` +exports[`snapshotAllPages TextInput 51`] = ` Date: Tue, 4 Nov 2025 10:43:53 +0530 Subject: [PATCH 12/19] chnaging the logic on synchronization between JS and Native side --- .../TextInput/WindowsTextInputComponentView.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp index fe85df3b120..e1a9b1f7d95 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp @@ -541,9 +541,12 @@ void WindowsTextInputComponentView::HandleCommand( std::optional text; winrt::Microsoft::ReactNative::ReadArgs(args.CommandArgs(), eventCount, text, begin, end); - // Only accept text updates that match the current native event count - // This prevents race conditions and maintains proper state synchronization - if (eventCount >= m_nativeEventCount) { + // Accept text updates that match current event count, or are within a reasonable + // tolerance for async JS responses to recent events (like onSubmitEditing) + bool isCurrentEvent = eventCount >= m_nativeEventCount; + bool isRecentAsyncResponse = (m_nativeEventCount - eventCount) <= 2; // Allow up to 2 events behind + + if (isCurrentEvent || isRecentAsyncResponse) { m_comingFromJS = true; { if (text.has_value()) { @@ -956,12 +959,8 @@ void WindowsTextInputComponentView::OnCharacterReceived( auto emitter = std::static_pointer_cast(m_eventEmitter); facebook::react::WindowsTextInputEventEmitter::OnSubmitEditing onSubmitEditingArgs; onSubmitEditingArgs.text = GetTextFromRichEdit(); - // Don't increment event count yet - let JS respond with current count first - onSubmitEditingArgs.eventCount = m_nativeEventCount; + onSubmitEditingArgs.eventCount = ++m_nativeEventCount; emitter->onSubmitEditing(onSubmitEditingArgs); - - // Increment after emitting to allow JS to respond with matching count - ++m_nativeEventCount; } if (m_clearTextOnSubmit) { From b9ff8426cfe9ff58f04e741cbe4ac165ab48e257 Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Tue, 4 Nov 2025 12:39:35 +0530 Subject: [PATCH 13/19] fixing linting error --- .../Composition/TextInput/WindowsTextInputComponentView.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp index e1a9b1f7d95..02b41733b7f 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp @@ -541,11 +541,11 @@ void WindowsTextInputComponentView::HandleCommand( std::optional text; winrt::Microsoft::ReactNative::ReadArgs(args.CommandArgs(), eventCount, text, begin, end); - // Accept text updates that match current event count, or are within a reasonable + // Accept text updates that match current event count, or are within a reasonable // tolerance for async JS responses to recent events (like onSubmitEditing) bool isCurrentEvent = eventCount >= m_nativeEventCount; bool isRecentAsyncResponse = (m_nativeEventCount - eventCount) <= 2; // Allow up to 2 events behind - + if (isCurrentEvent || isRecentAsyncResponse) { m_comingFromJS = true; { From 2068ef71a72e8fc882b54c541c8262d213f9ea56 Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Tue, 4 Nov 2025 13:56:05 +0530 Subject: [PATCH 14/19] removing e2e test --- .../test/TextInputComponentTest.test.ts | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/packages/e2e-test-app-fabric/test/TextInputComponentTest.test.ts b/packages/e2e-test-app-fabric/test/TextInputComponentTest.test.ts index 2c606ff540e..1894a4bca81 100644 --- a/packages/e2e-test-app-fabric/test/TextInputComponentTest.test.ts +++ b/packages/e2e-test-app-fabric/test/TextInputComponentTest.test.ts @@ -414,47 +414,6 @@ describe('TextInput Tests', () => { const dump = await dumpVisualTree('textinput-clear-on-submit-4'); expect(dump).toMatchSnapshot(); }); - test('TextInputs can manually clear text on submit using setValue in onSubmitEditing', async () => { - const component = await app.findElementByTestID( - 'textinput-manual-clear-on-submit', - ); - await component.waitForDisplayed({timeout: 5000}); - - // Enter text in the input - await app.waitUntil( - async () => { - await component.setValue('Hello World'); - return (await component.getText()) === 'Hello World'; - }, - { - interval: 1500, - timeout: 5000, - timeoutMsg: `Unable to enter correct text.`, - }, - ); - - // Verify text was entered - expect(await component.getText()).toBe('Hello World'); - - // Press Enter to trigger onSubmitEditing which should clear the text - await app.waitUntil( - async () => { - await component.setValue('\uE007'); // Enter key - return (await component.getText()) === ''; - }, - { - interval: 1500, - timeout: 5000, - timeoutMsg: `Text should be cleared after pressing Enter (manual setValue('') in onSubmitEditing)`, - }, - ); - - // Verify text was cleared - expect(await component.getText()).toBe(''); - - const dump = await dumpVisualTree('textinput-manual-clear-on-submit'); - expect(dump).toMatchSnapshot(); - }); test('TextInputs can keep text on focus', async () => { const componentFocusFalse = await app.findElementByTestID( 'clear-text-on-focus-false', From 6d1a24babe7ec0f171178740f555ca931debd34f Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Wed, 5 Nov 2025 07:38:09 +0530 Subject: [PATCH 15/19] changing synchronization logic between JS and Native side --- .../WindowsTextInputComponentView.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp index 6497ac8cb3f..5178fd58ae1 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp @@ -545,12 +545,21 @@ void WindowsTextInputComponentView::HandleCommand( std::optional text; winrt::Microsoft::ReactNative::ReadArgs(args.CommandArgs(), eventCount, text, begin, end); - // Accept text updates that match current event count, or are within a reasonable - // tolerance for async JS responses to recent events (like onSubmitEditing) + + // Standard synchronization check bool isCurrentEvent = eventCount >= m_nativeEventCount; - bool isRecentAsyncResponse = (m_nativeEventCount - eventCount) <= 2; // Allow up to 2 events behind + + // Special case: Allow setValue('') if it's responding to a recent onSubmitEditing event + // This is safe because clearing text doesn't depend on intermediate state + bool isSubmitClearResponse = false; + if (!isCurrentEvent && text.has_value() && winrt::to_string(text.value()).empty()) { + // Check if this could be a response to a recent onSubmitEditing event + // Only allow if the event is recent (within last 3 events) and we have clearTextOnSubmit behavior + isSubmitClearResponse = (m_nativeEventCount - eventCount) <= 3 && + (m_clearTextOnSubmit || m_lastSubmitEventCount == eventCount); + } - if (isCurrentEvent || isRecentAsyncResponse) { + if (isCurrentEvent || isSubmitClearResponse) { m_comingFromJS = true; { if (text.has_value()) { @@ -964,6 +973,7 @@ void WindowsTextInputComponentView::OnCharacterReceived( facebook::react::WindowsTextInputEventEmitter::OnSubmitEditing onSubmitEditingArgs; onSubmitEditingArgs.text = GetTextFromRichEdit(); onSubmitEditingArgs.eventCount = ++m_nativeEventCount; + m_lastSubmitEventCount = m_nativeEventCount; // Track this submit event emitter->onSubmitEditing(onSubmitEditingArgs); } From 73dc3d10e9d0890cccc1153d96bafe7d9a42e92b Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Wed, 5 Nov 2025 07:46:41 +0530 Subject: [PATCH 16/19] changing logic to avoid some difference between JS and Native layer event count --- .../TextInput/WindowsTextInputComponentView.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp index 5178fd58ae1..65e0822d482 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp @@ -546,17 +546,15 @@ void WindowsTextInputComponentView::HandleCommand( winrt::Microsoft::ReactNative::ReadArgs(args.CommandArgs(), eventCount, text, begin, end); - // Standard synchronization check + // Standard synchronization check - only accept current or future events bool isCurrentEvent = eventCount >= m_nativeEventCount; - // Special case: Allow setValue('') if it's responding to a recent onSubmitEditing event + // Special case: Allow setValue('') if it's responding to the exact onSubmitEditing event // This is safe because clearing text doesn't depend on intermediate state bool isSubmitClearResponse = false; if (!isCurrentEvent && text.has_value() && winrt::to_string(text.value()).empty()) { - // Check if this could be a response to a recent onSubmitEditing event - // Only allow if the event is recent (within last 3 events) and we have clearTextOnSubmit behavior - isSubmitClearResponse = (m_nativeEventCount - eventCount) <= 3 && - (m_clearTextOnSubmit || m_lastSubmitEventCount == eventCount); + // Only allow if this is responding to the exact submit event we fired + isSubmitClearResponse = (m_lastSubmitEventCount != -1 && eventCount == m_lastSubmitEventCount); } if (isCurrentEvent || isSubmitClearResponse) { @@ -578,6 +576,11 @@ void WindowsTextInputComponentView::HandleCommand( m_textServices->TxSendMessage(EM_SETSEL, static_cast(begin), static_cast(end), &res)); } + // Clear the submit event count after using it to prevent reuse + if (isSubmitClearResponse) { + m_lastSubmitEventCount = -1; + } + m_comingFromJS = false; } } From 877fd6a027c0aa49884239d2007b01eca8aef96a Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Wed, 5 Nov 2025 08:05:53 +0530 Subject: [PATCH 17/19] adding format change --- .../Composition/TextInput/WindowsTextInputComponentView.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp index 65e0822d482..013fd01a9db 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp @@ -545,10 +545,10 @@ void WindowsTextInputComponentView::HandleCommand( std::optional text; winrt::Microsoft::ReactNative::ReadArgs(args.CommandArgs(), eventCount, text, begin, end); - + // Standard synchronization check - only accept current or future events bool isCurrentEvent = eventCount >= m_nativeEventCount; - + // Special case: Allow setValue('') if it's responding to the exact onSubmitEditing event // This is safe because clearing text doesn't depend on intermediate state bool isSubmitClearResponse = false; @@ -976,7 +976,7 @@ void WindowsTextInputComponentView::OnCharacterReceived( facebook::react::WindowsTextInputEventEmitter::OnSubmitEditing onSubmitEditingArgs; onSubmitEditingArgs.text = GetTextFromRichEdit(); onSubmitEditingArgs.eventCount = ++m_nativeEventCount; - m_lastSubmitEventCount = m_nativeEventCount; // Track this submit event + m_lastSubmitEventCount = m_nativeEventCount; // Track this submit event emitter->onSubmitEditing(onSubmitEditingArgs); } From b1ef624d8f9cc92c170d7bf72ff44331733d9bc8 Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Wed, 5 Nov 2025 08:42:51 +0530 Subject: [PATCH 18/19] adding header and adding new field --- .../Fabric/Composition/TextInput/WindowsTextInputComponentView.h | 1 + 1 file changed, 1 insertion(+) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h index 9692f5ca351..59f8abf5d98 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h @@ -134,6 +134,7 @@ struct WindowsTextInputComponentView float m_fontSizeMultiplier{1.0}; int64_t m_mostRecentEventCount{0}; int m_nativeEventCount{0}; + int m_lastSubmitEventCount{-1}; // Track the event count of the last onSubmitEditing bool m_comingFromJS{false}; bool m_comingFromState{false}; int m_cDrawBlock{0}; From 802abdf7aa80f54d2f435b9d31585bd4d6c774bb Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Wed, 5 Nov 2025 08:58:32 +0530 Subject: [PATCH 19/19] fixing linting issue --- .../Composition/TextInput/WindowsTextInputComponentView.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h index 59f8abf5d98..40b0a68010a 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h @@ -134,7 +134,7 @@ struct WindowsTextInputComponentView float m_fontSizeMultiplier{1.0}; int64_t m_mostRecentEventCount{0}; int m_nativeEventCount{0}; - int m_lastSubmitEventCount{-1}; // Track the event count of the last onSubmitEditing + int m_lastSubmitEventCount{-1}; // Track the event count of the last onSubmitEditing bool m_comingFromJS{false}; bool m_comingFromState{false}; int m_cDrawBlock{0};