Mike Codeur Formations
Sign in
Mike Codeur Formations

By Mike Codeur

© 2026 Mike Codeur Formations. All rights reserved.

Product

  • Courses
  • Documentation
  • Blog

Legal & Contact

  • Legal Notice
  • Privacy Policy
  • Contact
Get Started

Ready to get started ?

Join thousands of users building amazing things with our platform.

Get Started Free

No credit card required

Newsletter

Stay updated with our latest articles and web development news.

No spam, unsubscribe anytime

Back to articlesTutorial

The Proxy Component in React

A simple pattern to shield your codebase from change: centralise an implementation instead of scattering it across every file.

#react#patterns#components
Mike Codeur
August 01, 20265 min
Tutorial
…
Written by
MC
Mike Codeur
Author
Published on August 01, 2026

Related Articles

Introduction to Next.js 16Development

Introduction to Next.js 16

What actually changes when you build with Next.js 16 and React 19: async params, Server Components, Server Actions and Turbopack.

Aug 03, 20265 min
Read more
Design
Design

Tailwind CSS v4: configuration moves into your CSS

No more tailwind.config.js. v4 moves the whole configuration into your stylesheet, with @theme, @plugin and @custom-variant.

Aug 02, 20264 min
Read more
Behind the intimidating name hides a very simple idea — and probably the React pattern that saves you the most time in the long run: never let an implementation spread across your codebase. A Proxy Component is a component whose only job is to wrap another one. It barely does anything. That is exactly why it is useful.

The problem

Take an app with "Like" buttons all over the place:
function Header() {
  return (
    <div>
      <h1>Welcome</h1>
      <button>Like</button>
    </div>
  )
}

function Content() {
  return (
    <div>
      <h2>Articles</h2>
      <span>Article 1</span>
      <button>Like</button>
      <span>Article 2</span>
      <button>Like</button>
    </div>
  )
}

function Footer() {
  return (
    <div>
      <h3>Contact us</h3>
      <button>Like</button>
    </div>
  )
}
All fine — until the day the button implementation has to change: switch to the company component library, add a class, wire up analytics tracking:
// before
<button>Like</button>

// after
<Button variant="ghost" size="sm" onClick={track}>Like</Button>
Now you are off on a find-and-replace across the whole project. With five occurrences, fine. With a hundred and fifty spread over forty files, that is a lost day — and a good chance of missing three of them.
The real cost is not the number of lines to change, it is the risk. Every missed occurrence becomes a visual inconsistency nobody notices until it is in production.

The fix

Create a component that does nothing but delegate:
function LikeButton(props) {
  return <button {...props}>Like</button>
}
Then use it everywhere:
function Header() {
  return (
    <div>
      <h1>Welcome</h1>
      <LikeButton />
    </div>
  )
}
At first glance you have only added indirection. In practice you have created a single point of change. The day the implementation evolves, exactly one file moves:
function LikeButton(props) {
  return (
    <Button variant="ghost" size="sm" {...props}>
      Like
    </Button>
  )
}
Nothing else changes. That is the whole point of the pattern.

The key detail: spreading props

The {...props} is not cosmetic — it is what makes the component genuinely reusable. Without it, your proxy becomes a wall: you cannot pass an onClick, a disabled or an aria-label without editing the wrapper every single time.
function LikeButton(props) {
  return <button {...props}>Like</button>
}

// everything works, no change to the proxy
<LikeButton onClick={handleLike} disabled={isPending} aria-label="Like" />
function LikeButton({onClick}) {
  return <button onClick={onClick}>Like</button>
}

// silently ignored: the proxy never forwards them
<LikeButton disabled={isPending} aria-label="Like" />
A proxy that does not forward its props is worse than no proxy at all: attributes vanish without any error. You end up debugging the parent when the bug lives in the wrapper.

In TypeScript

Type your props from the element you are wrapping and you inherit everything it accepts:
import type {ComponentProps} from 'react'

type LikeButtonProps = ComponentProps<'button'>

export function LikeButton(props: LikeButtonProps) {
  return <button {...props}>Like</button>
}
Same thing when wrapping an existing component rather than an HTML element:
import type {ComponentProps} from 'react'

import {Button} from '@/components/ui/button'

type LikeButtonProps = ComponentProps<typeof Button>

export function LikeButton(props: LikeButtonProps) {
  return (
    <Button variant="ghost" size="sm" {...props}>
      Like
    </Button>
  )
}
In React 19, ref is a regular prop: it is part of ComponentProps and travels through the spread. No more forwardRef needed to write a transparent proxy.

Prop order: who wins?

A detail that matters — where you place the spread decides who has the final say.
// defaults — the caller can override them
<Button variant="ghost" {...props} />

// enforced — the caller cannot change them
<Button {...props} variant="ghost" />
The first form is almost always the right one: it provides defaults without locking anything down. Keep the second for things that must never vary, such as a type="button" that prevents an accidental form submission.

Where it actually pays off

1

Wrapping a third-party library

Every button goes through your own component instead of the library's. The day you switch UI libraries, you rewrite a handful of files, not an application.
2

Enforcing a product convention

Every external link must carry a blank target and the matching rel? A proxy guarantees it once and for all, instead of relying on everyone's attention during code review.
3

Adding cross-cutting behaviour

Analytics, logging, loading state: the proxy is the natural place to add behaviour to every usage of a component at once.
4

Absorbing a migration

During an API change, the proxy translates the old interface into the new one. The rest of the codebase keeps running while you migrate.

A concrete example: the external link

import type {ComponentProps} from 'react'

type ExternalLinkProps = ComponentProps<'a'>

export function ExternalLink(props: ExternalLinkProps) {
  return <a target="_blank" rel="noopener noreferrer" {...props} />
}
Six lines, and a security hole (a missing rel on a blank target) becomes structurally impossible in your app.

When NOT to use it

The pattern is simple, therefore tempting. Two guardrails:
SituationVerdict
A component used in three or more placesGood candidate
A convention to guarantee (security, a11y)Good candidate, even for two usages
A single usage with no reuse in sightPointless — indirection for nothing
A proxy wrapping a proxy wrapping a proxy…Red flag: flatten it
The right question is not "does this repeat?" but "what is going to change?". You isolate what moves, not what looks alike.

Takeaway

A Proxy Component is three lines of code that turn a global change into a local one. It requires no library and no clever abstraction — just the discipline of never calling an implementation directly from forty different places. That is the real spirit of DRY: not "never write the same thing twice", but "have a single place to edit when a decision changes".