Function Components
A function component is the standard way to write React components today. It is a plain JavaScript function that accepts a props object and returns JSX describing what should appear on screen. No classes, no this, no lifecycle boilerplate — just a function.
// The simplest possible function component
function Hello() {
return <p>Hello, world!</p>
}Why Function Components Replaced Class Components
Before React 16.8 (released in 2019), stateful logic and lifecycle management required class components. Hooks changed that. With useState, useEffect, useContext, and the rest of the hooks API, function components can do everything class components could — with significantly less code and clearer logic flow.
Dimension | Function Component | Class Component |
|---|---|---|
Syntax | Plain function | class … extends React.Component |
State | useState / useReducer | this.state / this.setState |
Side effects | useEffect | componentDidMount / componentDidUpdate |
Code reuse | Custom hooks | Higher-order components / render props |
this keyword | Never needed | Required everywhere |
Bundle size | Smaller | Larger |
The Props Parameter
React calls your component function with a single argument: props, an object containing all the attributes the parent passed. You almost always destructure it immediately:
// Without destructuring — verbose
function Badge(props: { label: string; count: number }) {
return (
<span>
{props.label}: {props.count}
</span>
)
}
// With destructuring — cleaner, idiomatic React
function Badge({ label, count }: { label: string; count: number }) {
return (
<span>
{label}: {count}
</span>
)
}
// Usage
<Badge label="Messages" count={5} />Return Type: JSX.Element and ReactNode
TypeScript users can annotate the return type of a component. The two most common types are JSX.Element and React.ReactNode:
import { ReactNode } from 'react'
// JSX.Element — always returns a single JSX value (never null/undefined)
function Card(): JSX.Element {
return <div className="card" />
}
// ReactNode — broader: can return JSX, null, string, number, boolean, or an array
function MaybeLabel({ show }: { show: boolean }): ReactNode {
if (!show) return null
return <label>Visible</label>
}
// In practice, React 18+ infers the return type from the JSX,
// so you can omit the annotation and TypeScript will be happy:
function Heading({ text }: { text: string }) {
return <h1>{text}</h1>
}Regular Function vs Arrow Function
Both syntaxes work identically as React components. The choice is a style preference:
// Regular function declaration — hoisted, shows up in stack traces by name
function Button({ onClick, children }: ButtonProps) {
return <button onClick={onClick}>{children}</button>
}
// Arrow function expression — not hoisted, common in some codebases
const Button = ({ onClick, children }: ButtonProps) => (
<button onClick={onClick}>{children}</button>
)
// Arrow function with explicit return (useful for multi-line JSX)
const Button = ({ onClick, children }: ButtonProps) => {
return <button onClick={onClick}>{children}</button>
}Returning null — Conditional Rendering
A function component may return null to render nothing. This is the idiomatic way to hide a component conditionally without unmounting its parent:
function ErrorBanner({ message }: { message: string | null }) {
// Return null to render nothing — no DOM node is created
if (!message) return null
return (
<div className="error-banner" role="alert">
{message}
</div>
)
}
// In the parent, no conditional is needed — ErrorBanner handles it
function Form() {
const [error, setError] = useState<string | null>(null)
return (
<form>
<ErrorBanner message={error} />
{/* rest of form */}
</form>
)
}Fragment Shorthand
JSX requires a single root element. When you need to return multiple sibling elements without adding an extra <div> to the DOM, use a React Fragment:
// ✗ Syntax error — two root elements
function TwoThings() {
return (
<h1>Title</h1>
<p>Body</p>
)
}
// ✓ Wrap in a Fragment — no extra DOM node
function TwoThings() {
return (
<>
<h1>Title</h1>
<p>Body</p>
</>
)
}
// ✓ Long-form (needed when you need the key prop in a list)
import { Fragment } from 'react'
function ItemList({ items }: { items: string[] }) {
return (
<>
{items.map((item, i) => (
<Fragment key={i}>
<dt>{item}</dt>
<dd>Description of {item}</dd>
</Fragment>
))}
</>
)
}A Complete Stateful Function Component
Here is a realistic component that combines props, local state, an event handler, and conditional rendering:
import { useState } from 'react'
interface AccordionProps {
title: string
children: React.ReactNode
defaultOpen?: boolean
}
function Accordion({ title, children, defaultOpen = false }: AccordionProps) {
const [isOpen, setIsOpen] = useState(defaultOpen)
return (
<div className="accordion">
<button
className="accordion-header"
onClick={() => setIsOpen(prev => !prev)}
aria-expanded={isOpen}
>
<span>{title}</span>
<span aria-hidden="true">{isOpen ? '▲' : '▼'}</span>
</button>
{isOpen && (
<div className="accordion-body">
{children}
</div>
)}
</div>
)
}
// Usage
function FAQ() {
return (
<section>
<Accordion title="What is React?">
<p>React is a JavaScript library for building user interfaces.</p>
</Accordion>
<Accordion title="What are hooks?" defaultOpen>
<p>Hooks are functions that let you use React features in function components.</p>
</Accordion>
</section>
)
}Key Conventions at a Glance
PascalCase for component names — required, not optional
One component per file is the community standard (small helper components can co-locate)
Destructure props at the parameter list for readability
Return null to render nothing rather than wrapping in a conditional outside the component
Use fragments (
<></>) to avoid unnecessary DOM wrapper elementsKeep the function body pure — no side effects, no async/await, no random values