Learn

/

Default Values

Default Values

5 patterns

Setting sensible defaults via destructuring, stable references, and default callbacks.

Default prop values
easy
Avoid
interface BadgeProps {
  label: string;
  color?: string;
  size?: string;
}

Badge.defaultProps = {
  color: 'gray',
  size: 'md',
};

Prefer
interface BadgeProps {
  label: string;
  /** @default "gray" */
  color?: 'gray' | 'blue' | 'green' | 'red';
  /** @default "md" */
  size?: 'sm' | 'md' | 'lg';
}

function Badge({
  label,
  color = 'gray',
  size = 'md',
}: BadgeProps) {

defaultProps is deprecated in React 19 and will be removed. ES6 destructuring defaults are type-safe, colocated with the function, and work with TypeScript out of the box. Bonus: @default JSDoc tags document the defaults in IDE hover tooltips.

React 19: Removed Deprecated APIs
Safe default assignment
medium
Avoid
function Slider({
  min,
  max,
  value,
  label,
}: SliderProps) {
  const safeMin = min || 0;
  const safeMax = max || 100;
  const safeValue = value || 50;
  const safeLabel = label || 'Volume';
}

Prefer
function Slider({
  min = 0,
  max = 100,
  value = 50,
  label = 'Volume',
}: SliderProps) {
  // min, max, value, label are
  // guaranteed to be defined here.
}

Destructuring defaults only apply when the value is undefined — which is exactly what "not passed" means in React.

The || operator also triggers on 0, "", and false, which are legitimate values. min={0} would silently become 0 || 0 here, but value={0} would wrongly become 50.

MDN: Destructuring Default Values
Defaults in signature vs body
easy
Avoid
function Avatar({
  src,
  alt,
  size,
}: AvatarProps) {
  const imgSize = size ?? 40;
  const imgSrc = src ?? '/default-avatar.png';
  const imgAlt = alt ?? 'User avatar';

  return (
    <img
      src={imgSrc}
      alt={imgAlt}
      width={imgSize}
      height={imgSize}
    />
  );
}

Prefer
function Avatar({
  src = '/default-avatar.png',
  alt = 'User avatar',
  size = 40,
}: AvatarProps) {
  return (
    <img
      src={src}
      alt={alt}
      width={size}
      height={size}
    />
  );
}

Destructuring defaults apply when a prop is undefined — exactly what "not passed" means in React. This eliminates the intermediate variables and makes defaults visible in the function signature. Any developer reading the code instantly sees the fallback values.

MDN: Destructuring Default Values
Stable default object references
medium
Avoid
function DataGrid({
  columns,
  data,
  filters = {},
  sortOrder = [],
  pagination = { page: 1, pageSize: 20 },
}: DataGridProps) {
  useEffect(() => {
    fetchData({ filters, sortOrder, pagination });
  }, [filters, sortOrder, pagination]);
}

Prefer
const EMPTY_FILTERS: Filters = {};
const EMPTY_SORT: SortOrder[] = [];
const DEFAULT_PAGINATION: Pagination = {
  page: 1,
  pageSize: 20,
};

function DataGrid({
  columns,
  data,
  filters = EMPTY_FILTERS,
  sortOrder = EMPTY_SORT,
  pagination = DEFAULT_PAGINATION,
}: DataGridProps) {
  useEffect(() => {
    fetchData({ filters, sortOrder, pagination });
  }, [filters, sortOrder, pagination]);
}

Inline = {}, = [], and = { ... } create new object references on every render. If these defaults are passed to useEffect or useMemo dependency arrays, they'll trigger re-runs every time.

Module-level constants are created once and have stable references.

React Docs: useMemo troubleshooting
Default callbacks that skip null checks
hard
Avoid
interface FormFieldProps {
  label: string;
  value: string;
  onChange: (value: string) => void;
  onBlur?: () => void;
  onFocus?: () => void;
  validate?: (value: string) => string | null;
}

function FormField({
  onBlur,
  onFocus,
  validate,
  ...props
}: FormFieldProps) {
  const error = validate ? validate(props.value) : null;
  // Must null-check every callback before calling...
}

Prefer
const noop = () => {};
const noValidation = () => null;

interface FormFieldProps {
  label: string;
  value: string;
  onChange: (value: string) => void;
  /** @default noop */
  onBlur?: () => void;
  /** @default noop */
  onFocus?: () => void;
  /** @default () => null */
  validate?: (value: string) => string | null;
}

function FormField({
  onBlur = noop,
  onFocus = noop,
  validate = noValidation,
  ...props
}: FormFieldProps) {
  const error = validate(props.value);
  // No null checks — defaults handle missing callbacks.
}

Default callbacks (noop for side effects, null-returning functions for transforms) eliminate if (callback) checks throughout the component. The code reads linearly instead of branching on every optional callback. Module-level defaults also provide stable references for dependency arrays.

React Docs: Default Prop Values