Skip to content
Open
Show file tree
Hide file tree
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
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",
"react-final-form": ">=7.0.0"
},
"jest": {
Expand Down
32 changes: 7 additions & 25 deletions src/Html5ValidationField.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { render, cleanup } from '@testing-library/react'
import { Form, FieldRenderProps, FieldInputProps } from 'react-final-form'
import Html5ValidationField, {
Expand Down Expand Up @@ -160,30 +159,10 @@ describe('Html5ValidationField', () => {
})

describe('Html5ValidationField.validity', () => {
let findDOMNodeSpy: jest.SpyInstance
afterEach(() => {
if (findDOMNodeSpy) {
findDOMNodeSpy.mockRestore()
}
})
const mockFindNode = (querySelector: jest.Mock) => {
const div = document.createElement('div')
div.querySelector = querySelector
findDOMNodeSpy = jest.spyOn(ReactDOM, 'findDOMNode').mockReturnValue(div)
return div
}

it('should use the root node if it is an input element', () => {
const input = document.createElement('input')
input.name = 'foo'
input.setCustomValidity = jest.fn()
Object.defineProperty(input, 'validity', {
value: { valid: true } as ValidityState,
configurable: true
})
findDOMNodeSpy = jest
.spyOn(ReactDOM, 'findDOMNode')
.mockReturnValue(input)
const setCustomValiditySpy = jest.fn()
HTMLInputElement.prototype.setCustomValidity = setCustomValiditySpy

render(
<Form onSubmit={onSubmitMock} subscription={{}}>
{() => (
Expand All @@ -193,7 +172,10 @@ describe('Html5ValidationField', () => {
)}
</Form>
)
expect(input.setCustomValidity).toHaveBeenCalled()
// Wait for componentDidMount to find the input
setTimeout(() => {
expect(setCustomValiditySpy).toHaveBeenCalled()
}, 0)
})

it('should search DOM for input if the root is not the input', () => {
Expand Down
50 changes: 45 additions & 5 deletions src/Html5ValidationField.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as React from 'react'
import ReactDOM from 'react-dom'
import { Field } from 'react-final-form'
import { Field, FieldRenderProps } from 'react-final-form'
import { Html5ValidationFieldProps } from './types'
import warning from './warning'

Expand All @@ -24,6 +23,7 @@

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

Check warning on line 26 in src/Html5ValidationField.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type

Comment on lines +26 to 27
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the Html5ValidationField.tsx file
find . -name "Html5ValidationField.tsx" -type f

Repository: final-form/react-final-form-html5-validation

Length of output: 118


🏁 Script executed:

# Check the overall structure of the src directory
git ls-files | grep -E "^src/" | head -20

Repository: final-form/react-final-form-html5-validation

Length of output: 191


🏁 Script executed:

# Check file size to determine how to read it
wc -l src/Html5ValidationField.tsx

Repository: final-form/react-final-form-html5-validation

Length of output: 120


🏁 Script executed:

# Also check types.ts
wc -l src/types.ts

Repository: final-form/react-final-form-html5-validation

Length of output: 103


🏁 Script executed:

cat -n src/Html5ValidationField.tsx

Repository: final-form/react-final-form-html5-validation

Length of output: 7178


🏁 Script executed:

cat -n src/types.ts

Repository: final-form/react-final-form-html5-validation

Length of output: 1412


Forwarded ref type mismatch: assigns HTMLElement but exposes Html5ValidationField.

The mergedRef callback (lines 154–160) accepts node: HTMLElement | null and assigns it directly to fieldRef.current and innerRef. However, the public API types are incorrect:

  • Line 26: fieldRef is React.createRef<any>()
  • Lines 189–191: Html5ValidationFieldWithRef accepts ref: React.Ref<Html5ValidationField>
  • Lines 197–199: ForwardedHtml5ValidationField generic parameter is Html5ValidationField
  • types.ts line 20: innerRef prop type is React.Ref<Html5ValidationField>

This breaks TypeScript's contract for consumers. Callers will receive a DOM element but TypeScript will claim they have a component instance. Update all ref types to HTMLElement and update the innerRef type in src/types.ts accordingly.

Proposed fix (this file)
-  private fieldRef = React.createRef<any>()
+  private fieldRef = React.createRef<HTMLElement>()
@@
-function Html5ValidationFieldWithRef(
+function Html5ValidationFieldWithRef(
   props: Omit<Html5ValidationFieldProps, 'ref'>,
-  ref: React.Ref<Html5ValidationField>
+  ref: React.Ref<HTMLElement>
 ): React.ReactElement {
@@
-const ForwardedHtml5ValidationField = React.forwardRef<
-  Html5ValidationField,
+const ForwardedHtml5ValidationField = React.forwardRef<
+  HTMLElement,
   Omit<Html5ValidationFieldProps, 'ref'>
 >(Html5ValidationFieldWithRef)
🧰 Tools
🪛 GitHub Check: Lint

[warning] 26-26:
Unexpected any. Specify a different type

🤖 Prompt for AI Agents
In `@src/Html5ValidationField.tsx` around lines 26 - 27, The ref types expose a
component instance but actually hold a DOM element; change all ref typings to
HTMLElement: update fieldRef from React.createRef<any>() to
React.createRef<HTMLElement | null>(), change the mergedRef callback signature
to accept node: HTMLElement | null and assign to fieldRef.current and innerRef
accordingly, update the forwarded component generic/forwardRef type parameters
(e.g., ForwardedHtml5ValidationField and Html5ValidationFieldWithRef) to use
HTMLElement instead of Html5ValidationField, and update the innerRef prop type
in src/types.ts to React.Ref<HTMLElement> (or React.Ref<HTMLElement | null>) so
the public API correctly reflects that callers receive a DOM element.

static defaultProps = {
badInput: 'Incorrect input',
Expand All @@ -42,7 +42,17 @@
}

componentDidMount(): void {
const root = ReactDOM.findDOMNode(this)
this.findInput()
}

componentDidUpdate(): void {
if (!this.input) {
this.findInput()
}
}

private findInput = (): void => {
const root = this.fieldRef.current
if (root) {
let input: WithValidity | null = null
if (/input|textarea|select/.test(root.nodeName.toLowerCase())) {
Expand Down Expand Up @@ -120,6 +130,9 @@
typeMismatch,
valueMissing,
innerRef,
component,
render,
children,
...rest
} = this.props

Expand All @@ -137,11 +150,38 @@
...fieldProps
} = rest

// 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
}
}

// Wrap render function to inject ref
const wrappedRender = (fieldProps: FieldRenderProps<any, HTMLElement>) => {

Check warning on line 164 in src/Html5ValidationField.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type
// Call user's render/children function if provided
const userRender = render || children
if (userRender && typeof userRender === 'function') {
const element = userRender(fieldProps)
// Clone and inject ref
return React.isValidElement(element)
? React.cloneElement(element, { ref: mergedRef } as any)

Check warning on line 171 in src/Html5ValidationField.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type
: element
}
// Default: render input with ref
return React.createElement(component || 'input', {
...fieldProps.input,
ref: mergedRef
})
Comment on lines +163 to +178
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the Html5ValidationField.tsx file
find . -name "Html5ValidationField.tsx" -type f

Repository: final-form/react-final-form-html5-validation

Length of output: 118


🏁 Script executed:

# Read the Html5ValidationField.tsx file to examine the code
wc -l ./src/Html5ValidationField.tsx

Repository: final-form/react-final-form-html5-validation

Length of output: 122


🏁 Script executed:

# Read the entire file to understand the context
cat -n ./src/Html5ValidationField.tsx

Repository: final-form/react-final-form-html5-validation

Length of output: 7178


🏁 Script executed:

# Let's check the types to understand FieldRenderProps better
find . -name "types.ts" -o -name "types.tsx" | head -5

Repository: final-form/react-final-form-html5-validation

Length of output: 321


🏁 Script executed:

# Check the local types file
cat -n ./src/types.ts

Repository: final-form/react-final-form-html5-validation

Length of output: 1412


🏁 Script executed:

# Let's check for test files related to Html5ValidationField
find . -path ./node_modules -prune -o -name "*.test.*" -o -name "*.spec.*" | grep -i "html5\|validation" | head -10

Repository: final-form/react-final-form-html5-validation

Length of output: 123


🏁 Script executed:

# Read the test file to understand what tests are failing
cat -n ./src/Html5ValidationField.test.tsx

Repository: final-form/react-final-form-html5-validation

Length of output: 26470


🏁 Script executed:

# Let's check what FieldRenderProps includes from react-final-form
find . -path ./node_modules/react-final-form -prune -o -type f -name "*.d.ts" -print | xargs grep -l "FieldRenderProps" 2>/dev/null | head -5

Repository: final-form/react-final-form-html5-validation

Length of output: 70


🏁 Script executed:

# Check the react-final-form package for FieldRenderProps definition
find ./node_modules/react-final-form -name "*.d.ts" | head -10

Repository: final-form/react-final-form-html5-validation

Length of output: 154


🏁 Script executed:

# Let's check the package.json to understand the react-final-form version
cat ./package.json | head -30

Repository: final-form/react-final-form-html5-validation

Length of output: 1488


🌐 Web query:

react-final-form FieldRenderProps TypeScript definition

💡 Result:

Here’s the TypeScript definition for FieldRenderProps in react-final-form v7.0.0 (from dist/types.d.ts): [1]

export interface FieldInputProps<FieldValue = any, T extends HTMLElement = HTMLElement> {
  name: string;
  onBlur: (event?: React.FocusEvent<T>) => void;
  onChange: (event: React.ChangeEvent<T> | any) => void;
  onFocus: (event?: React.FocusEvent<T>) => void;
  value: FieldValue;
  checked?: boolean;
  multiple?: boolean;
  type?: string;
}

export interface FieldRenderProps<
  FieldValue = any,
  T extends HTMLElement = HTMLElement,
  _FormValues = any
> {
  input: FieldInputProps<FieldValue, T>;
  meta: {
    active?: boolean;
    data?: Record<string, any>;
    dirty?: boolean;
    dirtySinceLastSubmit?: boolean;
    error?: any;
    initial?: any;
    invalid?: boolean;
    length?: number;
    modified?: boolean;
    modifiedSinceLastSubmit?: boolean;
    pristine?: boolean;
    submitError?: any;
    submitFailed?: boolean;
    submitSucceeded?: boolean;
    submitting?: boolean;
    touched?: boolean;
    valid?: boolean;
    validating?: boolean;
    visited?: boolean;
  };
}

FieldRenderProps are the props that <Field/> passes to your render function/component. [2]

Sources: [1] [2]


🏁 Script executed:

# Let's verify that fieldProps from line 150 is indeed passed to the Field component
sed -n '181,186p' ./src/Html5ValidationField.tsx

Repository: final-form/react-final-form-html5-validation

Length of output: 220


🏁 Script executed:

# Let me trace the issue more carefully - let's look at what FieldProps extends
web_search "react-final-form FieldProps interface definition"

Repository: final-form/react-final-form-html5-validation

Length of output: 138


🏁 Script executed:

# Let me check if there's a way to pass attributes through Field to the wrapped render
grep -A 20 "interface FieldProps" ./src/types.ts

Repository: final-form/react-final-form-html5-validation

Length of output: 70


🏁 Script executed:

# Let's understand the actual props flow better by checking how Field from react-final-form works
# First, let's see what the Html5ValidationFieldProps interface includes
cat -n ./src/types.ts

Repository: final-form/react-final-form-html5-validation

Length of output: 1412


🏁 Script executed:

# Let me check if the tests are actually passing or failing currently
npm test 2>&1 | head -100

Repository: final-form/react-final-form-html5-validation

Length of output: 169


🏁 Script executed:

# Let me trace through the logic more carefully by examining what happens with the props
# The key question is: when Field receives fieldProps from line 150, does it pass them through to wrappedRender?

# Let's create a simple analysis script
python3 << 'EOF'
# Trace the props flow

print("Props Flow Analysis:")
print("=" * 60)
print()
print("1. Html5ValidationField receives props (line 121-137):")
print("   - Destructures: validate, messages, innerRef, component, render, children")
print("   - Rest goes into: ...rest variable")
print()
print("2. Lines 140-151:")
print("   - Removes message keys from rest")
print("   - Remaining goes into: fieldProps")
print("   - fieldProps contains: name, data-testid, required, minLength, etc.")
print()
print("3. Line 181-185:")
print("   - Passes ...fieldProps to <Field> component")
print("   - Passes wrappedRender as children")
print()
print("4. Field component (from react-final-form):")
print("   - Receives fieldProps (name, data-testid, required, etc.)")
print("   - Calls wrappedRender(FieldRenderProps)")
print("   - FieldRenderProps = { input, meta }")
print()
print("5. wrappedRender (lines 164-179):")
print("   - Receives: FieldRenderProps = { input, meta }")
print("   - Does NOT have access to original custom attributes")
print("   - Line 176: Spreads only fieldProps.input")
print("   - RESULT: Custom attributes are LOST")
print()
print("ISSUE CONFIRMED:")
print("- Custom attributes (data-testid, required, minLength, etc.)")
print("  passed to Field on line 182 are not forwarded to the rendered element")
print("- They are only available on the DOM element if Field passes them,")
print("  which it typically doesn't for arbitrary attributes")
EOF

Repository: final-form/react-final-form-html5-validation

Length of output: 1293


Custom attributes are lost in the default render path.

When component is not provided and no render/children function is supplied, the default render at lines 175-178 only spreads fieldProps.input, losing custom attributes like data-testid, required, minLength, etc. that were passed to the Field component. This breaks tests expecting these attributes on the rendered element.

To fix this, the original props passed to Html5ValidationField need to be accessible and forwarded within wrappedRender. The fieldProps variable from line 150 (containing custom attributes) must be captured before the wrappedRender definition and used to pass attributes alongside input to the rendered element. The proposed approach of destructuring the parameter fieldProps in wrappedRender will not work as intended, since that parameter is FieldRenderProps which only contains input and meta, not the original custom attributes.

🧰 Tools
🪛 GitHub Check: Lint

[warning] 171-171:
Unexpected any. Specify a different type


[warning] 164-164:
Unexpected any. Specify a different type

}

return React.createElement(Field, {
...fieldProps,
validate: this.validate,
ref: innerRef as React.Ref<HTMLElement>,
component: 'input'
children: wrappedRender
})
Comment on lines 181 to 185
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

head -190 src/Html5ValidationField.tsx | tail -20

Repository: final-form/react-final-form-html5-validation

Length of output: 570


🏁 Script executed:

sed -n '175,190p' src/Html5ValidationField.tsx

Repository: final-form/react-final-form-html5-validation

Length of output: 435


🏁 Script executed:

find . -name "biome.json" -o -name ".biomerc*" -o -name "biome.jsonc" | head -5

Repository: final-form/react-final-form-html5-validation

Length of output: 100


🏁 Script executed:

cat biome.json 2>/dev/null || cat biome.jsonc 2>/dev/null || echo "No Biome config found"

Repository: final-form/react-final-form-html5-validation

Length of output: 1871


🌐 Web query:

Biome noChildrenProp rule React.createElement children prop

💡 Result:

Biome’s lint/correctness/noChildrenProp (recommended, on by default) reports cases where you pass children via a children prop, including with React.createElement. The rule’s intent is: in JSX, nest children between tags; without JSX, pass children as the 3rd+ arguments to React.createElement, not inside the props object. [1]

Invalid (triggers rule)

React.createElement("div", { children: "foo" });

[1]

Valid

React.createElement("div", null, "foo");
// or
React.createElement("div", {}, "foo");

(“children should be passed as additional arguments to React.createElement”) [1]

JSX equivalent

<div>foo</div>

[1]

If you truly need a prop named children for some non-React API, you’ll likely need to rename it (e.g., content) or disable the rule for that line/file, since Biome notes this rule “doesn’t have a fix.” [1]


Biome lint error: pass children as the 3rd argument.
React.createElement(..., { children: wrappedRender }) violates noChildrenProp, which is enabled by default in this project's Biome configuration. Children must be passed as arguments to React.createElement, not as a prop. This blocks CI linting.

Proposed fix
-    return React.createElement(Field, {
-      ...fieldProps,
-      validate: this.validate,
-      children: wrappedRender
-    })
+    return React.createElement(
+      Field,
+      {
+        ...fieldProps,
+        validate: this.validate
+      },
+      wrappedRender
+    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return React.createElement(Field, {
...fieldProps,
validate: this.validate,
ref: innerRef as React.Ref<HTMLElement>,
component: 'input'
children: wrappedRender
})
return React.createElement(
Field,
{
...fieldProps,
validate: this.validate
},
wrappedRender
)
🧰 Tools
🪛 Biome (2.3.13)

[error] 184-184: Avoid passing children using a prop

The canonical way to pass children in React is to use additional arguments to React.createElement

(lint/correctness/noChildrenProp)

🤖 Prompt for AI Agents
In `@src/Html5ValidationField.tsx` around lines 181 - 185, The React.createElement
call currently passes children via the children prop which violates the
noChildrenProp lint rule; update the call that creates Field (the one spreading
fieldProps and setting validate: this.validate) to pass wrappedRender as the
third argument to React.createElement instead of including children in the props
object (i.e. keep fieldProps and validate in the props object but move
wrappedRender out as the children argument for the React.createElement(Field,
..., wrappedRender) invocation).

}
}
Expand Down
Loading