Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 87 additions & 2 deletions packages/react-dom-bindings/src/shared/ReactDOMFormActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@
*
* @flow
*/

import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes';
import type {Awaited} from 'shared/ReactTypes';

import ReactSharedInternals from 'shared/ReactSharedInternals';
import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';

Expand Down Expand Up @@ -64,6 +62,86 @@ function resolveDispatcher() {
return ((dispatcher: any): Dispatcher);
}

/**
* Helper function to determine if a form field is controlled
* A field is considered controlled if it has a value or checked prop from React
*/
function isControlledField(element: HTMLElement): boolean {
// Get the React fiber node associated with this DOM element
const fiber = element._reactInternalFiber || element.__reactInternalInstance;

if (!fiber) {
return false;
}

// Check for controlled props on the fiber's memoizedProps
const props = fiber.memoizedProps;
if (!props) {
return false;
}

// For input elements, check for 'value' or 'checked' props
if (element.tagName === 'INPUT') {
const inputType = element.type;
if (inputType === 'checkbox' || inputType === 'radio') {
return props.hasOwnProperty('checked');
} else {
return props.hasOwnProperty('value');
}
}

// For textarea and select elements, check for 'value' prop
if (element.tagName === 'TEXTAREA' || element.tagName === 'SELECT') {
return props.hasOwnProperty('value');
}

return false;
}

/**
* Reset form fields after a successful form action
* Only reset uncontrolled fields, skip controlled fields
*/
function resetFormFieldsAfterAction(form: HTMLFormElement): void {
const formElements = form.elements;

for (let i = 0; i < formElements.length; i++) {
const element = formElements[i] as HTMLElement;

// Skip if this is a controlled field
if (isControlledField(element)) {
if (__DEV__) {
console.log('Skipping reset for controlled field:', element);
}
continue;
}

// Reset uncontrolled fields as before
if (element.tagName === 'INPUT') {
const inputElement = element as HTMLInputElement;
const inputType = inputElement.type;

if (inputType === 'checkbox' || inputType === 'radio') {
inputElement.checked = inputElement.defaultChecked;
} else if (inputType !== 'submit' && inputType !== 'button' && inputType !== 'reset') {
inputElement.value = inputElement.defaultValue;
}
} else if (element.tagName === 'TEXTAREA') {
const textareaElement = element as HTMLTextAreaElement;
textareaElement.value = textareaElement.defaultValue;
} else if (element.tagName === 'SELECT') {
const selectElement = element as HTMLSelectElement;
selectElement.selectedIndex = selectElement.defaultSelected ? selectElement.selectedIndex : 0;

// Reset individual options
for (let j = 0; j < selectElement.options.length; j++) {
const option = selectElement.options[j];
option.selected = option.defaultSelected;
}
}
}
}

export function useFormStatus(): FormStatus {
const dispatcher = resolveDispatcher();
return dispatcher.useHostTransitionStatus();
Expand All @@ -79,6 +157,13 @@ export function useFormState<S, P>(
}

export function requestFormReset(form: HTMLFormElement) {
// Reset form fields with controlled/uncontrolled logic
resetFormFieldsAfterAction(form);

// Call the original reset functionality
ReactDOMSharedInternals.d /* ReactDOMCurrentDispatcher */
.r(/* requestFormReset */ form);
}

// Export the helper function for potential external use
export {isControlledField, resetFormFieldsAfterAction};