Skip to content

autofocus not working on page mount #85

Open
@chiragksharma

Description

Expected Behavior

When using the autoFocus prop on the CustomOtpInput component, the first OTP input field should automatically receive focus when the component mounts so that the user can immediately start typing the OTP.

Current Behavior

The autoFocus prop does not work as expected. Even when set to true, the first OTP input field is not automatically focused when the component is rendered. Attempts to manually focus the input via a ref (or using a setTimeout inside a useEffect) have also failed to yield the desired result.

Code Example:

<CustomOtpInput 
            autoFocus 
            value={otp} 
            onChange={(value) => setOtp(value)} 
            onComplete={handleOtpComplete}
          />

The component code

'use client';

import React, { useEffect, useRef } from 'react';
import { OTPInput, SlotProps } from 'input-otp';
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

// Utility to merge class names
function cn(...inputs: any[]) {
  return twMerge(clsx(inputs));
}

// Slot component for each OTP digit
function Slot(props: SlotProps) {
  return (
    <div
      className={cn(
        'relative w-10 h-14 text-[2rem]',
        'flex items-center justify-center',
        'transition-all duration-300',
        'border-border border-y border-r first:border-l first:rounded-l-md last:rounded-r-md',
        'group-hover:border-border/60 group-focus-within:border-border/60',
        'outline outline-0 outline-border/70',
        { 'outline-4 outline-border': props.isActive }
      )}
    >
      <div className="group-has-[input[data-input-otp-placeholder-shown]]:opacity-20">
        {props.char ?? props.placeholderChar}
      </div>
      {props.hasFakeCaret && <FakeCaret />}
    </div>
  );
}

// Component to emulate a blinking caret
function FakeCaret() {
  return (
    <div className="absolute pointer-events-none inset-0 flex items-center justify-center animate-caret-blink">
      <div className="w-px h-8 bg-foreground" />
    </div>
  );
}

// Component for the dash separator between groups of OTP slots
function FakeDash() {
  return (
    <div className="flex w-10 justify-center items-center">
      <div className="w-3 h-1 rounded-full bg-border" />
    </div>
  );
}

// Define the props for our OTP input component
interface OtpInputProps {
  value: string;
  onChange: (value: string) => void;
  maxLength?: number;
  containerClassName?: string;
  autoFocus?: boolean;
  onComplete?: () => void;
}

// The custom OTP input component
const CustomOtpInput: React.FC<OtpInputProps> = ({
  value,
  onChange,
  maxLength = 6,
  containerClassName = "group flex items-center has-[:disabled]:opacity-30",
  autoFocus = true,
  onComplete,
}) => {
  // Create a ref for the container wrapping the OTPInput
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (autoFocus && containerRef.current) {
      // Wait a tick to ensure the inputs are rendered
      setTimeout(() => {
        // Query for the first <input> element inside the container
        const input = containerRef.current!.querySelector('input');
        if (input) {
          input.focus();
        }
      }, 0);
    }
  }, [autoFocus]);

  return (
    <div ref={containerRef}>
      <OTPInput
        autoFocus={autoFocus} 
        onComplete={onComplete}
        maxLength={maxLength}
        value={value}
        onChange={onChange}
        containerClassName={containerClassName}
        render={({ slots }) => (
          <>
            <div className="flex">
              {slots.slice(0, 3).map((slot, idx) => (
                <Slot key={idx} {...slot} />
              ))}
            </div>

            <FakeDash />

            <div className="flex">
              {slots.slice(3).map((slot, idx) => (
                <Slot key={idx} {...slot} />
              ))}
            </div>
          </>
        )}
      />
    </div>
  );
};

export default CustomOtpInput;

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions