<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: D Keshav L</title>
    <description>The latest articles on DEV Community by D Keshav L (@dkeshavl).</description>
    <link>https://dev.to/dkeshavl</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4078667%2Fed1a67c3-d511-409e-a30e-efecae515225.png</url>
      <title>DEV Community: D Keshav L</title>
      <link>https://dev.to/dkeshavl</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dkeshavl"/>
    <language>en</language>
    <item>
      <title>Refactoring a Prop-Heavy Table Into Compound Components (React + Context)</title>
      <dc:creator>D Keshav L</dc:creator>
      <pubDate>Fri, 21 Aug 2026 10:51:09 +0000</pubDate>
      <link>https://dev.to/dkeshavl/refactoring-a-prop-heavy-table-into-compound-components-react-context-ima</link>
      <guid>https://dev.to/dkeshavl/refactoring-a-prop-heavy-table-into-compound-components-react-context-ima</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn4yfne3xs28yaaa1a71y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn4yfne3xs28yaaa1a71y.png" alt="My Table Had 8 PROPS. NOW IT HAS 1" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a component I'm not proud of, from a few weeks ago:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;DataTable
  data={users}
  search={search}
  setSearch={setSearch}
  filter={filter}
  setFilter={setFilter}
  columns={columns}
  page={page}
  pageSize={10}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eight props. And every parent that rendered this table had to declare search/setSearch and filter/setFilter state itself, just to hand it back down. The table wasn't managing its own state — the parent was doing that work and the table was just consuming the result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6zwtqsf6bm7u8b45pkfk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6zwtqsf6bm7u8b45pkfk.png" alt="THE PROPS ARE GONE.THE STATE IS SHARED THROUGH CONTEXT." width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The refactor&lt;/p&gt;

&lt;p&gt;I split it into compound components sharing state through Context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DataTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Root&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DataTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Search&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DataTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Filter&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;DataTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Root&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One prop on the outer component. Here's roughly what's underneath:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const TableContext = createContext(null);

function Root({ data, children }) {
  const [search, setSearch] = useState('');
  const [filter, setFilter] = useState('all');

  const filtered = data.filter((row) =&amp;gt; {
    const matchesSearch = row.name.toLowerCase().includes(search.toLowerCase());
    const matchesFilter = filter === 'all' || row.status === filter;
    return matchesSearch &amp;amp;&amp;amp; matchesFilter;
  });

  return (
    &amp;lt;TableContext.Provider value={{ search, setSearch, filter, setFilter, filtered }}&amp;gt;
      {children}
    &amp;lt;/TableContext.Provider&amp;gt;
  );
}

function useTable() {
  const ctx = useContext(TableContext);
  if (!ctx) throw new Error('DataTable.* must be rendered inside DataTable.Root');
  return ctx;
}

Root.Search = function Search() {
  const { search, setSearch } = useTable();
  return &amp;lt;input value={search} onChange={(e) =&amp;gt; setSearch(e.target.value)} placeholder="Search…" /&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Search&lt;/code&gt; never sees a &lt;code&gt;search&lt;/code&gt; or &lt;code&gt;setSearch&lt;/code&gt; prop. It reaches into context directly. Same for &lt;code&gt;Filter&lt;/code&gt;. The parent doesn't need to know any of this exists — it just passes &lt;code&gt;data&lt;/code&gt; once and gets a working table.&lt;/p&gt;

&lt;h3&gt;
  
  
  What actually changed, mechanically
&lt;/h3&gt;

&lt;p&gt;Nothing about React itself changed. This is &lt;code&gt;createContext&lt;/code&gt; + &lt;code&gt;useContext&lt;/code&gt; wrapped in a hook, plus a naming convention (&lt;code&gt;DataTable.Search&lt;/code&gt; as a static property on the &lt;code&gt;Root&lt;/code&gt; export) that makes the API read like a mini design system instead of a pile of loose exports.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trade-offs I ran into
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Implicit dependency.&lt;/strong&gt; &lt;code&gt;DataTable.Search&lt;/code&gt; throws if it's rendered outside &lt;code&gt;DataTable.Root&lt;/code&gt;. That's the right failure mode — loud and immediate instead of silently broken — but it means the components can't be used standalone anymore. If you need a search input that works with or without the table context, this isn't the right shape for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Harder to trace state.&lt;/strong&gt; With props, you can follow state by reading the component tree top to bottom. With context, you have to know to look for a Provider somewhere above. For a junior dev unfamiliar with the pattern, that's a real onboarding cost, not just a style preference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not free performance-wise.&lt;/strong&gt; Every consumer of &lt;code&gt;TableContext&lt;/code&gt; re-renders when any value in the context changes. For a component this small it doesn't matter. For a bigger tree, you'd want to split the context or memoize consumers — something to watch if you scale this pattern up.&lt;/p&gt;

&lt;h3&gt;
  
  
  When I'd use this vs. plain props
&lt;/h3&gt;

&lt;p&gt;Compound components make sense when multiple related pieces need to share state and you want their JSX arrangement to stay flexible — search, filter, and a table body are a reasonable case. Plain props are still the better default when a component does one job and its shape isn't going to change. Don't add a &lt;code&gt;Root&lt;/code&gt; + Context layer just because it looks more "advanced" — that's ceremony without a payoff.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5d9td17wxmu1dydrh7cl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5d9td17wxmu1dydrh7cl.png" alt="Compound Components vs Props" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it
&lt;/h3&gt;

&lt;p&gt;Live: &lt;a href="https://compound-table.vercel.app/" rel="noopener noreferrer"&gt;https://compound-table.vercel.app/&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://github.com/dkeshavl/compound-table" rel="noopener noreferrer"&gt;https://github.com/dkeshavl/compound-table&lt;/a&gt; (React 18, Tailwind, Vite)&lt;br&gt;
Support : &lt;a href="https://buymeacoffee.com/dkeshavl" rel="noopener noreferrer"&gt;https://buymeacoffee.com/dkeshavl&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What would you have done differently — split the context further, add a reducer instead of multiple &lt;code&gt;useState&lt;/code&gt; calls, something else?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjx0hbwe82ax2bpgfbyky.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjx0hbwe82ax2bpgfbyky.png" alt="Support" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>compoundcomponents</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
