iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Props

Props are inputs to a component — read-only, flow downward, and trigger re-renders when they change. Get props right (destructure, default sensibly, type carefully) and components stay composable.

Destructure, defaults, children, types

EXAMPLE
// 1) Basic — destructure at the parameter
function Greeting({ name, role = 'guest' }) {
    return <p>Hi {name} ({role})</p>;
}
<Greeting name="Mara" role="admin" />
<Greeting name="Anon" />            // role -> 'guest'

// 2) children is just another prop
function Card({ title, children }) {
    return (
        <section className="card">
            <h3>{title}</h3>
            <div>{children}</div>
        </section>
    );
}
<Card title="Profile"><p>email: …</p></Card>

// 3) Spread props — forward + override
function Input({ className, ...rest }) {
    return <input className={`base ${className ?? ''}`} {...rest} />;
}
<Input type="email" required autoComplete="email" />

// 4) Render props pattern — child as function
function MouseTracker({ children }) {
    const [pos, setPos] = useState({ x: 0, y: 0 });
    return (
        <div onMouseMove={(e) => setPos({ x: e.clientX, y: e.clientY })}>
            {children(pos)}
        </div>
    );
}
<MouseTracker>{({ x, y }) => <p>{x}, {y}</p>}</MouseTracker>

// 5) Passing objects vs primitives
// New object identity each render breaks shallow-equal memoization.
const onSave = useCallback((id) => save(id), []);
const config = useMemo(() => ({ limit: 10 }), []);
<Row onSave={onSave} config={config} />

// 6) Required vs optional with TypeScript
interface Props {
    title: string;             // required
    subtitle?: string;          // optional
    onClick: () => void;        // required
    items?: Array<{ id: string; name: string }>;
}

function List({ title, subtitle, onClick, items = [] }: Props) {
    return (
        <section>
            <h2>{title}</h2>
            {subtitle && <p>{subtitle}</p>}
            <ul>{items.map((i) => <li key={i.id} onClick={onClick}>{i.name}</li>)}</ul>
        </section>
    );
}

// 7) Forwarding refs through a component
import { forwardRef } from 'react';
const FancyInput = forwardRef<HTMLInputElement, { label: string }>(
    function FancyInput({ label }, ref) {
        return (
            <label>
                {label}
                <input ref={ref} className="fancy" />
            </label>
        );
    },
);
// Parent: const inputRef = useRef<HTMLInputElement>(null);
//   <FancyInput ref={inputRef} label="Email" />

// 8) Discriminated unions for variant components
type AlertProps =
    | { variant: 'success'; message: string }
    | { variant: 'error';   message: string; retry: () => void };

function Alert(p: AlertProps) {
    if (p.variant === 'error') return <button onClick={p.retry}>{p.message}</button>;
    return <p>{p.message}</p>;
}

// 9) Default props for class components — discouraged for function components
// (Use parameter defaults instead.)

// 10) Avoid prop drilling — Context / state lib when depth > 3-4
const ThemeContext = createContext({ theme: 'light' });
function useTheme() { return useContext(ThemeContext); }

// 11) Common bugs
//   • Mutating props (props.items.push(...)) — props are READ-ONLY
//   • New literal each render -> child memo never hits ({ limit: 10 }, [])
//   • Defaults in body 'props.role || "guest"' — fine; prefer parameter defaults
//   • Index as key in lists that reorder — wrong state attaches
//   • Passing children as a prop name OTHER than 'children' — works but loses convention
//   • forwardRef forgotten — parent ref is undefined
//   • Spreading {...rest} onto a DOM node with non-DOM props — React warns about unknown attribute

Why it matters

Props are read-only inputs — destructure at the parameter, default sensibly, and TypeScript them generously. When you find yourself drilling props more than three levels, the answer is usually Context or composition, not yet another prop.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
function Avatar({ src, alt, size = 32 }) {
    return <img src={src} alt={alt} width={size} height={size} />;
}
Try it Yourself »

Discussion

Loading…