Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
"peerDependencies": {
"final-form": ">=5.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^18.2.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"react-final-form": ">=7.0.0"
},
"jest": {
Expand Down
18 changes: 15 additions & 3 deletions src/Html5ValidationField.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as React from 'react'
import ReactDOM from 'react-dom'
import { Field } from 'react-final-form'
import { Html5ValidationFieldProps } from './types'
import warning from './warning'
Expand All @@ -24,6 +23,7 @@ interface WithValidity {

class Html5ValidationField extends React.Component<Html5ValidationFieldProps> {
private input: WithValidity | null = null
private rootRef = React.createRef<HTMLElement>()

static defaultProps = {
badInput: 'Incorrect input',
Expand All @@ -42,7 +42,7 @@ class Html5ValidationField extends React.Component<Html5ValidationFieldProps> {
}

componentDidMount(): void {
const root = ReactDOM.findDOMNode(this)
const root = this.rootRef.current
if (root) {
let input: WithValidity | null = null
if (/input|textarea|select/.test(root.nodeName.toLowerCase())) {
Expand Down Expand Up @@ -137,10 +137,22 @@ class Html5ValidationField extends React.Component<Html5ValidationFieldProps> {
...fieldProps
} = rest

// Merge innerRef with rootRef for internal use
const mergedRef = (node: HTMLElement | null) => {
// Set internal ref
(this.rootRef as React.MutableRefObject<HTMLElement | null>).current = node
// Call innerRef if provided
if (typeof innerRef === 'function') {
innerRef(node)
} else if (innerRef) {
(innerRef as React.MutableRefObject<HTMLElement | null>).current = node
}
}
Comment on lines +148 to +156

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

mergedRef is recreated on every render — causes spurious detach/attach for function refs.

Because mergedRef is defined inside render(), its identity changes on every render. React responds by invoking the previous ref with null and the new ref with the node on every commit. For consumers that pass innerRef as a callback function, this means they will be called with (null) then (node) on every parent re-render even though the underlying DOM node never changed — easy to mistake for a remount and a regression vs. the prior findDOMNode-based behavior.

Promote mergedRef to a stable class field that reads this.props.innerRef at call time:

♻️ Proposed refactor
 class Html5ValidationField extends React.Component<Html5ValidationFieldProps> {
   private input: WithValidity | null = null
   private fieldRef = React.createRef<HTMLElement | null>()
+
+  private mergedRef = (node: HTMLElement | null): void => {
+    ;(this.fieldRef as React.MutableRefObject<HTMLElement | null>).current = node
+    const { innerRef } = this.props
+    if (typeof innerRef === 'function') {
+      innerRef(node)
+    } else if (innerRef) {
+      ;(innerRef as React.MutableRefObject<HTMLElement | null>).current = node
+    }
+  }
@@
-    // Merge innerRef with fieldRef
-    const mergedRef = (node: HTMLElement | null) => {
-      ;(this.fieldRef as React.MutableRefObject<HTMLElement | null>).current =
-        node
-      if (typeof innerRef === 'function') {
-        innerRef(node)
-      } else if (innerRef) {
-        ;(innerRef as React.MutableRefObject<HTMLElement | null>).current = node
-      }
-    }
+    const mergedRef = this.mergedRef
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Html5ValidationField.tsx` around lines 148 - 156, mergedRef is being
recreated inside render(), causing ref detach/attach cycles for function refs;
move mergedRef out of render into a stable class field (e.g., define mergedRef =
(node: HTMLElement | null) => { ... } on the class) so its identity is stable
across renders, and inside that handler read this.props.innerRef at call time
and update this.fieldRef accordingly (preserve existing behavior of calling
function refs or setting MutableRefObject.current). Also ensure the class-field
handler uses the same types as the current inline version and does not capture
stale props.


return React.createElement(Field, {
...fieldProps,
validate: this.validate,
ref: innerRef as React.Ref<HTMLElement>,
ref: mergedRef,
component: 'input'
})
}
Expand Down
Loading