Skip to content

React · Components

React Props and Component Types — A Complete Interview Guide

7 min read Updated 2026-06-23 Share:

Practice Props and Component Types interview questions

The mental model for props

Props are the mechanism React uses to pass data down through the component tree. They are received as a plain JavaScript object and are read-only — a component must never mutate its own props.

// Parent passes data down
<UserCard name="Alice" role="admin" isActive />

// Child receives a frozen snapshot
function UserCard({ name, role, isActive }) {
  return (
    <div className={`card ${isActive ? 'active' : ''}`}>
      <strong>{name}</strong> — {role}
    </div>
  )
}

The parent-owns-the-data rule has one consequence that trips up beginners: if a child needs to trigger a change visible to the parent, it does so by calling a callback function that the parent passed as a prop. Data flows down; events flow up through callbacks.

Function components vs class components

This is one of the most common React interview questions. The short answer: use function components — they cover everything except error boundaries.

// Function component (modern)
function Counter() {
  const [n, setN] = useState(0)
  return <button onClick={() => setN(n + 1)}>{n}</button>
}

// Class component (legacy)
class Counter extends React.Component {
  state = { n: 0 }
  render() {
    return (
      <button onClick={() => this.setState({ n: this.state.n + 1 })}>
        {this.state.n}
      </button>
    )
  }
}

The full comparison:

FeatureFunctionClass
StateuseState / useReducerthis.state / this.setState
LifecycleuseEffectcomponentDidMount, componentDidUpdate, componentWillUnmount
ContextuseContextstatic contextType / <Context.Consumer>
Error boundary❌ Not supportedcomponentDidCatch
this binding issues❌ None✅ Must bind handlers

The one remaining reason for class components: error boundaries. Only a class component can implement componentDidCatch and getDerivedStateFromError. In practice, most teams write one <ErrorBoundary> class component and use react-error-boundary everywhere else.

The children prop

Any content placed between a component's opening and closing tags flows in as the children prop. It's just a prop with special JSX syntax.

function Card({ title, children }) {
  return (
    <section className="card">
      <h2>{title}</h2>
      <div className="body">{children}</div>
    </section>
  )
}

// The <p> and <button> become children
<Card title="Account">
  <p>Your subscription is active.</p>
  <button>Manage</button>
</Card>

children enables composition — the Card component knows its shell but nothing about its contents. This is much more flexible than a data prop: <Card content={<p>…</p>} /> works too, but the nested tag form reads better.

children can be: a string, a single element, an array of elements, a function (render prop), or undefined. Use React.Children.count(children) to check how many children were passed.

Default props

The idiomatic modern approach is default parameter destructuring:

function Button({ label = 'Submit', variant = 'primary', disabled = false }) {
  return (
    <button className={`btn btn-${variant}`} disabled={disabled}>
      {label}
    </button>
  )
}

This is co-located with the parameter list and works with TypeScript type annotations without any React-specific API. The legacy Button.defaultProps object still works but is considered outdated and may be removed in a future React version.

PropTypes

PropTypes is a runtime prop validation library (separate prop-types package) that logs warnings in development when props fail the declared type:

import PropTypes from 'prop-types'

function Avatar({ src, alt, size }) {
  return <img src={src} alt={alt} width={size} height={size} />
}

Avatar.propTypes = {
  src: PropTypes.string.isRequired,
  alt: PropTypes.string.isRequired,
  size: PropTypes.number,
}

Avatar.defaultProps = { size: 48 }

The trade-off with TypeScript: PropTypes catch mistakes at runtime in development; TypeScript catches them at compile time. In a TypeScript project, TypeScript alone is sufficient. In a plain JavaScript project, PropTypes are a lightweight safety net.

Props vs state

PropsState
Owned byParentThe component itself
Mutable byParent onlyThe component (setState / setter)
PurposePass configuration inTrack data that changes

A useful mental model: props are like function arguments (passed in, fixed per call); state is like local variables that persist between calls.

function Thermometer({ unit }) {      // unit is a prop
  const [temp, setTemp] = useState(20) // temp is state
  return (
    <div>
      {temp}°{unit}
      <button onClick={() => setTemp(t => t + 1)}>+</button>
    </div>
  )
}

Prop drilling and how to avoid it

Prop drilling happens when you pass a prop through several components that don't use it — just to get it to a distant descendant.

// Drilling `user` through Layout and Sidebar just to reach Avatar
<App user={user}>
  <Layout user={user}>
    <Sidebar user={user}>
      <Avatar user={user} />
    </Sidebar>
  </Layout>
</App>

Alternatives:

  1. Composition with children — pass <Avatar user={user} /> as a child of App; Layout and Sidebar receive it as children and never see user.
  2. ContextReact.createContext + useContext for genuinely global data (current user, theme, locale).
  3. State management — Zustand, Redux for data needed by many unrelated components.

Before reaching for Context, try restructuring with composition — it's often simpler and the data flow stays explicit.

Spread props pattern

Forward a batch of unknown props to a child without listing them:

function TextInput({ label, error, ...inputProps }) {
  return (
    <div>
      <label>{label}</label>
      <input {...inputProps} />
      {error && <span className="error">{error}</span>}
    </div>
  )
}

// All native <input> attributes work without boilerplate
<TextInput label="Email" type="email" name="email" required error={errors.email} />

Always destructure known props first, then spread the rest. Spreading the entire props object onto a DOM element can forward non-standard props, which React warns about.

Composition over inheritance

React's component model is built on composition, not inheritance. The docs explicitly discourage using class inheritance between your own components.

// ✅ Composition — SuccessDialog wraps Dialog
function SuccessDialog({ message }) {
  return (
    <Dialog icon="check" title="Done">
      <p>{message}</p>
    </Dialog>
  )
}

// ❌ Inheritance — unusual and discouraged
class SuccessDialog extends Dialog { … }

Composition strategies:

  • Children prop — generic shell that accepts any content.
  • Specialized wrappers — one component renders another with pre-set props.
  • Render props / hooks — share logic without shared structure.

Rule of thumb: when you think "I want a component like X but with Y changed," reach for a wrapper with props or children before reaching for a class hierarchy.

Controlled components

A controlled component's form value is driven entirely by React state. React becomes the single source of truth.

function EmailInput() {
  const [email, setEmail] = useState('')
  return (
    <input
      type="email"
      value={email}
      onChange={e => setEmail(e.target.value)}
    />
  )
}

Every keystroke calls setEmail, which updates state, which re-renders, which sets value — a tight loop. The payoff: you can validate or transform input on every change, and programmatic resets work (setEmail('') clears the field).

Uncontrolled components let the DOM own the value, accessed via useRef at submission time. Use them when the form is simple and you only need values at submit.

What interviewers are testing

Props questions check whether you understand the unidirectional data flow contract that makes React predictable. Class-vs-function questions check whether you know modern React. Children and composition questions check whether you reach for the right tool instead of prop-drilling or inheritance.

Know the read-only rule, the flow-down principle, and why function components replaced classes for all but error boundaries — and you'll answer this category confidently.

More ways to practice

The self-quiz is live. Get notified when mock interviews and new question packs drop.

or
Join our WhatsApp Channel