Skip to content

Enhance TextField component with ref support and update stories#96

Merged
jaruesink merged 3 commits into
mainfrom
mohsen/360t-220-add-ref-support-to-textfield-component-in-forms-repo
Jun 7, 2025
Merged

Enhance TextField component with ref support and update stories#96
jaruesink merged 3 commits into
mainfrom
mohsen/360t-220-add-ref-support-to-textfield-component-in-forms-repo

Conversation

@lcmohsen

@lcmohsen lcmohsen commented Jun 5, 2025

Copy link
Copy Markdown
Contributor
  • Refactored TextField for better ref handling.
  • Added a new example in the storybook to demonstrate the ref functionality with a focus button.
  • Updated TextInput to also support refs, ensuring consistent behavior across components.

Summary by CodeRabbit

  • New Features

    • Added a controlled text field with a button to programmatically focus the input in the form example.
  • Refactor

    • Updated text field components to support React refs, enabling programmatic focus and better integration.
    • Set display names on components for improved debugging and React DevTools identification.

- Refactored TextField to use React.forwardRef for better ref handling.
- Added a new example in the storybook to demonstrate the ref functionality with a focus button.
- Updated TextInput to also support refs, ensuring consistent behavior across components.
@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@coderabbitai

coderabbitai Bot commented Jun 5, 2025

Copy link
Copy Markdown

Walkthrough

The changes refactor several text field components to support React ref forwarding by explicitly accepting and passing refs to underlying input elements. A new example demonstrates using a ref to programmatically focus an input. Component signatures and prop types are updated to enable this functionality, along with adding display names for better identification.

Changes

Files Change Summary
packages/components/src/ui/text-input.tsx Extended InputProps with optional ref, updated TextInput to accept and forward ref, added displayName.
packages/components/src/ui/text-field.tsx Modified TextField to accept forwarded ref, updated components.Input prop type to include refs.
packages/components/src/remix-hook-form/text-field.tsx Changed TextField to a const function with ref prop, forwarded ref, added displayName.
apps/docs/src/remix-hook-form/text-field.stories.tsx Added controlled text field example using a React ref and a button to focus the input programmatically.
packages/components/package.json Bumped package version from 0.15.1 to 0.15.2.

Sequence Diagram(s)

sequenceDiagram
    participant ParentComponent
    participant TextField
    participant TextInput
    participant InputElement

    ParentComponent->>TextField: Pass ref via prop
    TextField->>TextInput: Forward ref
    TextInput->>InputElement: Attach ref to <input>
    ParentComponent->>InputElement: Call focus() via ref
Loading

Suggested reviewers

  • jaruesink

Poem

🐇 Hopping through code with nimble grace,
Refs now guide the input’s place.
Focus jumps with just one click,
Forwarded refs make it slick.
Display names shine bright and clear,
A rabbit’s joy to see them here! 🌸✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0b7c46 and 0db2c8d.

📒 Files selected for processing (1)
  • packages/components/package.json (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/components/package.json
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: build
  • GitHub Check: test

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions

github-actions Bot commented Jun 5, 2025

Copy link
Copy Markdown
Contributor

📝 Storybook Preview: View Storybook

This preview will be updated automatically when you push new changes to this PR.

Note: The preview will be available after the workflow completes and the PR is approved for deployment.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/docs/src/remix-hook-form/text-field.stories.tsx (1)

64-68: Consider adding refExample to form schema.

The ref functionality demonstration is well implemented with proper optional chaining. However, the refExample field is not included in the form schema, so it won't be validated or submitted with the form.

If this is intentional for demo purposes, consider adding a comment to clarify. Otherwise, add it to the schema:

const formSchema = z.object({
  username: z.string().min(3, 'Username must be at least 3 characters'),
  price: z.string().min(1, 'Price is required'),
  email: z.string().email('Invalid email address'),
  measurement: z.string().min(1, 'Measurement is required'),
+ refExample: z.string().optional(),
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 879e7e4 and 45b628c.

📒 Files selected for processing (4)
  • apps/docs/src/remix-hook-form/text-field.stories.tsx (3 hunks)
  • packages/components/src/remix-hook-form/text-field.tsx (2 hunks)
  • packages/components/src/ui/text-field.tsx (2 hunks)
  • packages/components/src/ui/text-input.tsx (2 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: jaruesink
PR: lambda-curry/forms#14
File: packages/components/src/ui/textarea.tsx:17-24
Timestamp: 2024-11-26T05:28:58.779Z
Learning: In `packages/components/src/ui/textarea.tsx`, the `Textarea` component forwards its ref to `FormItem`, and the textarea element receives `field.ref`, so the ref forwarding works correctly.
packages/components/src/ui/text-input.tsx (1)
Learnt from: jaruesink
PR: lambda-curry/forms#14
File: packages/components/src/ui/textarea.tsx:17-24
Timestamp: 2024-11-26T05:28:58.779Z
Learning: In `packages/components/src/ui/textarea.tsx`, the `Textarea` component forwards its ref to `FormItem`, and the textarea element receives `field.ref`, so the ref forwarding works correctly.
packages/components/src/ui/text-field.tsx (1)
Learnt from: jaruesink
PR: lambda-curry/forms#14
File: packages/components/src/ui/textarea.tsx:17-24
Timestamp: 2024-11-26T05:28:58.779Z
Learning: In `packages/components/src/ui/textarea.tsx`, the `Textarea` component forwards its ref to `FormItem`, and the textarea element receives `field.ref`, so the ref forwarding works correctly.
packages/components/src/remix-hook-form/text-field.tsx (1)
Learnt from: jaruesink
PR: lambda-curry/forms#14
File: packages/components/src/ui/textarea.tsx:17-24
Timestamp: 2024-11-26T05:28:58.779Z
Learning: In `packages/components/src/ui/textarea.tsx`, the `Textarea` component forwards its ref to `FormItem`, and the textarea element receives `field.ref`, so the ref forwarding works correctly.
🧬 Code Graph Analysis (1)
packages/components/src/remix-hook-form/text-field.tsx (1)
packages/components/src/ui/text-field.tsx (1)
  • TextField (67-111)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: test
🔇 Additional comments (11)
apps/docs/src/remix-hook-form/text-field.stories.tsx (2)

5-5: LGTM! Proper import for ref functionality.

The useRef import is correctly added to support the new ref demonstration.


42-42: LGTM! Correct ref typing.

The ref is properly typed as HTMLInputElement which matches the underlying input element.

packages/components/src/ui/text-input.tsx (3)

1-1: LGTM! Correct React import for forwardRef.

The import is correctly changed from type-only to full import to support React.forwardRef.


6-19: LGTM! Excellent forwardRef implementation.

The component is correctly converted to use React.forwardRef with proper typing for HTMLInputElement. The ref is properly forwarded to the underlying input element.


20-20: LGTM! Good debugging practice.

Adding displayName improves the development experience and React DevTools identification.

packages/components/src/remix-hook-form/text-field.tsx (3)

1-1: LGTM! Necessary React import.

The React import is correctly added to support React.forwardRef.


9-26: LGTM! Clean ref forwarding wrapper.

The component is correctly converted to use React.forwardRef and properly forwards the ref to the underlying BaseTextField. The ref forwarding chain is maintained without disrupting the existing component composition logic.


28-28: LGTM! Appropriate displayName.

The RemixTextField displayName clearly distinguishes this wrapper from the base TextField component.

packages/components/src/ui/text-field.tsx (3)

1-1: LGTM! Required React import change.

The import is correctly changed to support React.forwardRef.


60-60: LGTM! Proper type enhancement for ref support.

The Input component type is correctly updated to accept React.RefAttributes<HTMLInputElement>, ensuring type safety for ref forwarding.


67-111: LGTM! Comprehensive forwardRef implementation.

The component is expertly converted to use React.forwardRef while preserving all existing functionality:

  • Proper type parameters for HTMLInputElement and TextInputProps
  • Ref correctly forwarded to the InputComponent
  • All form field rendering logic maintained intact
  • Supports both default TextInput and custom input components

The ref forwarding chain is now complete from parent components down to the actual DOM input element.

@@ -1,11 +1,12 @@
import type * as React from 'react';
import * as React from 'react';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this line could still be "type" right?

export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}

function TextInput({ className, type, ...props }: InputProps) {
const TextInput = React.forwardRef<HTMLInputElement, InputProps>(({ className, type, ...props }, ref) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@lcmohsen instead of using forward Ref, can we follow React 19's patterns for passing in refs?

Comment on lines +67 to +69
export const TextField = React.forwardRef<HTMLInputElement, TextInputProps>(
({ control, name, label, description, className, components, prefix, suffix, ...props }, ref) => {
// Use the custom Input component if provided, otherwise use the default TextInput

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

instead of adding forward ref, can we pass in ref as a prop for React 19 patterns?

export type TextFieldProps = Omit<BaseTextFieldProps, 'control'>;

export function TextField(props: TextFieldProps) {
export const TextField = React.forwardRef<HTMLInputElement, TextFieldProps>((props, ref) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

instead of using forwardRef let's use ref for React 19 patterns

- Changed imports from React to type imports for better type safety.
- Refactored TextField to use a standard function instead of React.forwardRef, enhancing clarity.
- Updated TextInput to support ref prop directly, ensuring consistent ref handling across components.
@codegen-sh

codegen-sh Bot commented Jun 7, 2025

Copy link
Copy Markdown
Contributor

🔍 PR Review Summary

Great work on adding ref support to the TextField components! The functionality works well and the storybook example clearly demonstrates the feature. However, since this project is using React 19, there are some opportunities to modernize the implementation based on React 19's new ref patterns.

✅ What's Working Well

  • Functional Implementation: The ref forwarding works correctly and the storybook example demonstrates the feature effectively
  • Comprehensive Coverage: All relevant components (TextInput, TextField, RemixTextField) have been updated
  • Good Documentation: The storybook example with the focus button is a great way to showcase the functionality
  • Proper Display Names: Adding displayName properties improves debugging experience

🚀 Suggested Improvements

Based on the feedback from @jaruesink and React 19 best practices, here are the key areas for improvement:

1. Adopt React 19 Ref Patterns

Since this project uses React 19 ("react": "^19.0.0"), we can eliminate forwardRef and use the simpler ref-as-prop pattern. This makes the code cleaner and more maintainable.

2. Optimize TypeScript Imports

Keep imports as type imports where possible for better bundle optimization.

3. Consistent Pattern Application

Apply the new ref pattern consistently across all components in the PR.

📋 Specific Action Items

I'll provide detailed line-by-line feedback in the review comments, but the main changes needed are:

  1. Replace forwardRef usage with direct ref prop handling
  2. Update TypeScript interfaces to properly type the ref prop
  3. Maintain type imports where appropriate
  4. Ensure the storybook example continues to work with the new pattern

The React 19 approach is much cleaner - instead of wrapping components with forwardRef, we can simply accept ref as a regular prop and pass it directly to the underlying DOM element.

This modernization will make the codebase more future-proof and easier to maintain! 🎯

@@ -1,11 +1,14 @@
import type * as React from 'react';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You're absolutely right! This should remain as a type import for better TypeScript optimization and smaller bundle size. The type import tells TypeScript this is only used for type checking and won't be included in the runtime bundle.

import type * as React from 'react';

}

function TextInput({ className, type, ...props }: InputProps) {
function TextInput({ className, type, ref, ...props }: InputProps) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Perfect suggestion! With React 19, we can eliminate forwardRef entirely and use the much cleaner ref-as-prop pattern. Here's how this should look:

export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  ref?: React.Ref<HTMLInputElement>;
}

function TextInput({ className, type, ref, ...props }: InputProps) {
  return (
    <input
      ref={ref}
      type={type}
      className={cn(
        'flex h-10 w-full text-base sm:text-sm rounded-md border border-input bg-background px-3 py-2 ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
        className,
      )}
      {...props}
    />
  );
}

This is much cleaner and leverages React 19's native ref prop support!


export const TextField = ({
export const TextField = function TextField({
control,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Exactly! Let's apply the React 19 ref-as-prop pattern here as well. This will be much cleaner:

export interface TextInputProps extends Omit<InputProps, 'prefix' | 'suffix'> {
  control: Control<any>;
  name: string;
  label?: string;
  description?: string;
  components?: Partial<FieldComponents> & {
    Input?: React.ComponentType<InputProps & { ref?: React.Ref<HTMLInputElement> }>;
  };
  prefix?: React.ReactNode;
  suffix?: React.ReactNode;
  className?: string;
  ref?: React.Ref<HTMLInputElement>;
}

export const TextField = function TextField({
  control,
  name,
  label,
  description,
  className,
  components,
  prefix,
  suffix,
  ref,
  ...props
}: TextInputProps) {
  // ... rest of implementation
  return (
    <Controller
      control={control}
      name={name}
      render={({ field, fieldState }) => (
        // ... existing JSX with ref passed to InputComponent
        <InputComponent
          {...field}
          {...props}
          ref={ref}
          // ... rest of props
        />
      )}
    />
  );
};

This eliminates the need for forwardRef and makes the component much more straightforward!

export type TextFieldProps = Omit<BaseTextFieldProps, 'control'>;

export function TextField(props: TextFieldProps) {
export const TextField = function RemixTextField(props: TextFieldProps & { ref?: React.Ref<HTMLInputElement> }) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Perfect suggestion! Let's apply the React 19 pattern here too. The remix-hook-form TextField should also use the ref-as-prop pattern:

import type * as React from 'react';
import { TextField as BaseTextField, type TextInputProps as BaseTextFieldProps } from '../ui/text-field';
import { FormControl, FormDescription, FormLabel, FormMessage } from './form';
import { useRemixFormContext } from 'remix-hook-form';

export type TextFieldProps = Omit<BaseTextFieldProps, 'control'> & {
  ref?: React.Ref<HTMLInputElement>;
};

export const TextField = function RemixTextField({ ref, ...props }: TextFieldProps) {
  const { control } = useRemixFormContext();

  // Merge the provided components with the default form components
  const defaultComponents = {
    FormControl,
    FormLabel,
    FormDescription,
    FormMessage,
  };

  const components = {
    ...defaultComponents,
    ...props.components,
  };

  return <BaseTextField control={control} components={components} ref={ref} {...props} />;
};

TextField.displayName = 'RemixTextField';

This maintains consistency with the React 19 pattern across all TextField components!

@jaruesink jaruesink merged commit 1c5a4e6 into main Jun 7, 2025
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants