How to Control Focus or Disabled State of a Child Input from a Parent

Intermediate10 min interview
Skills tested:
Using forwardRef to forward a ref from parent to child DOM elementUsing useImperativeHandle to expose a curated imperative API from a child componentKnowing when to use controlled props (disabled) vs imperative methods (focus)Calling focus() on a child input from a parent button clickUnderstanding the difference between the declarative approach (props) and imperative approach (refs)

Advertisement

🧩 Scenario

Controlling a child input from a parent arises in two distinct patterns. The declarative pattern (disabled state) passes a boolean prop down; the child renders accordingly. The imperative pattern (focus, select, scroll) requires ref forwarding because focus() is a DOM method with no prop equivalent. Knowing which pattern to apply is the key judgment call.

Architecture Walkthrough

Two Patterns: Declarative and Imperative

Controlling a child component from a parent falls into two categories. Declarative control means the parent passes a prop (like disabled) and the child renders accordingly. This is the React-preferred approach because data flows down through props and the child's state is determined entirely by its props.

Imperative control means the parent calls a method on the child (like focus(), select(), or scrollIntoView()). These are actions, not state, and have no natural prop equivalent. React provides forwardRef and useImperativeHandle for this pattern.

forwardRef

By default, refs cannot be attached to custom function components. forwardRef wraps a component to accept a second ref argument alongside props, and forwards that ref to an internal DOM element. From the parent, ref.current becomes the DOM node and all its native methods are available.

useImperativeHandle

useImperativeHandle allows a child to expose a curated interface through a ref rather than the raw DOM element. Instead of giving the parent unrestricted access to the DOM node, the child defines exactly which methods it exposes. This is better practice for shared components: the contract is explicit and the child can change its implementation without breaking the parent.


Key Code Explained

// Pattern 1: Declarative — disabled is a prop
interface ControlledInputProps {
  disabled?: boolean;
  placeholder?: string;
}

function ControlledInput({ disabled = false, placeholder }: ControlledInputProps) {
  return (
    <input
      type="text"
      disabled={disabled}        // prop drives the disabled state
      placeholder={placeholder}
      className="form-input"
    />
  );
}

function ParentDeclarative() {
  const [isDisabled, setIsDisabled] = useState(false);

  return (
    <div>
      <ControlledInput disabled={isDisabled} placeholder="Enter text..." />
      <button onClick={() => setIsDisabled((d) => !d)}>
        {isDisabled ? 'Enable' : 'Disable'} Input
      </button>
    </div>
  );
}


// Pattern 2: Imperative — focus via forwardRef
interface FocusableInputRef {
  focus: () => void;
  select: () => void;
}

interface FocusableInputProps {
  placeholder?: string;
  disabled?: boolean;
}

// forwardRef lets the parent attach a ref to this component
const FocusableInput = forwardRef<FocusableInputRef, FocusableInputProps>(
  ({ placeholder, disabled = false }, ref) => {
    const inputRef = useRef<HTMLInputElement>(null);

    // Expose only the methods the parent should be able to call
    useImperativeHandle(
      ref,
      () => ({
        focus: () => inputRef.current?.focus(),
        select: () => inputRef.current?.select(),
        // blur() and setValue() are NOT exposed — the parent cannot call them
      }),
      [], // stable: these methods don't depend on any changing values
    );

    return (
      <input
        ref={inputRef}
        type="text"
        disabled={disabled}
        placeholder={placeholder}
        className="form-input"
      />
    );
  },
);

FocusableInput.displayName = 'FocusableInput';


function ParentImperative() {
  const inputRef = useRef<FocusableInputRef>(null);
  const [isDisabled, setIsDisabled] = useState(false);

  const handleFocusClick = () => {
    inputRef.current?.focus(); // calls the method exposed by useImperativeHandle
  };

  const handleSelectClick = () => {
    inputRef.current?.select();
  };

  return (
    <div>
      <FocusableInput
        ref={inputRef}                  // attaches via forwardRef
        disabled={isDisabled}          // disabled: still a prop, not imperative
        placeholder="Type to search..."
      />
      <button onClick={handleFocusClick}>Focus Input</button>
      <button onClick={handleSelectClick}>Select All</button>
      <button onClick={() => setIsDisabled((d) => !d)}>
        {isDisabled ? 'Enable' : 'Disable'}
      </button>
    </div>
  );
}

The disabled state in the imperative example is still passed as a prop, not through the ref. This is intentional: disabled is state (it should cause a re-render when it changes) and follows the declarative pattern. Only focus and select use the ref because they are one-time actions with no state change.


Tradeoffs

ApproachMechanismBest forReact style
Prop (disabled)DeclarativeState-driven behavior (disabled, hidden, value)Idiomatic
forwardRefImperative (raw DOM)When parent needs full DOM accessUse carefully
useImperativeHandleImperative (curated)Exposing a specific API from a child componentPreferred for custom components

What Interviewers Actually Check

  • Whether you know the declarative prop approach for state-driven behavior like disabled
  • Whether you know when to use forwardRef and useImperativeHandle for imperative actions like focus
  • Whether you know that refs cannot be attached to custom components without forwardRef
  • Whether you know useImperativeHandle lets the child control what the parent can call
  • Whether you set displayName on forwardRef components for DevTools readability

Follow-Up Questions

  1. React 19 introduces ref as a regular prop (no forwardRef wrapper needed). How does this change the pattern?
  2. How would you use useImperativeHandle to expose a validate() method that returns a boolean from a child form?
  3. When might you use a callback ref (a function as the ref prop) instead of useRef?
  4. How does useImperativeHandle interact with React.memo: if the parent re-renders, does the child's handle change?
  5. How would you animate a child element with GSAP from a parent, and which approach (forwardRef or a prop) would you choose?

Common Candidate Mistakes

  • Using imperative refs for disabled state when a prop is simpler and more idiomatic
  • Trying to attach a ref to a custom component without forwardRef and getting null with no clear error in development
  • Exposing the entire DOM node via forwardRef when useImperativeHandle would provide a more controlled interface
  • Not checking ref.current?. before calling methods, throwing on null when the component is not yet mounted
  • Forgetting to add displayName to forwardRef components, making debugging in React DevTools harder

Interview Readiness Checklist

Before you leave this question, make sure you can answer:

  • Can you use forwardRef to forward a ref from a parent to a child DOM element?
  • Can you use useImperativeHandle to expose only focus() and select() from a child?
  • Can you use a controlled prop for disabled state from the parent?
  • Can you explain the declarative vs imperative tradeoff and when to use each?
  • Can you call child methods from a parent button click handler using the ref?

Summary

Controlling a child input from a parent uses two different patterns depending on what is being controlled. State-driven behavior like disabled, value, or placeholder is declarative: the parent passes a prop and the child renders according to that prop. This is idiomatic React and requires no special APIs.

Imperative actions like focus() and select() have no prop equivalent and require refs. forwardRef wraps the child component to accept a ref argument alongside props, forwarding it to an internal DOM element. useImperativeHandle is the better approach for shared components: instead of exposing the raw DOM node, the child defines exactly which methods the parent can call, creating a stable and intentional contract.

In React 19, ref becomes a regular prop and forwardRef is no longer needed. Until then, the forwardRef + useImperativeHandle combination is the production pattern for any component that exposes imperative DOM operations to its parent.

Frequently Asked Questions

When should I use forwardRef vs passing a prop?

Use a prop for disabled and other state-driven behavior (declarative). Use forwardRef and useImperativeHandle for imperative DOM methods like focus(), scroll(), or select() that have no prop equivalent.

Advertisement


Stay Updated

Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.

Advertisement