React.Fragment — salvation from div hell or how to stop polluting the DOM?

Thu, May 8, 2025 - 6 min read
React Fragment

React.Fragment — salvation from div hell or how to stop polluting the DOM

Unnecessary divs in code annoy me. Seriously. When I open DevTools and see a nesting doll of meaningless wrappers — I want to cry.

Every unnecessary div is not just garbage in the DOM, it is pain for the developer and browser.


😤 Why divs annoy me

Typical picture in DevTools:

<div class="wrapper">
  <div class="container">
    <div class="inner">
      <div class="content">
        <div class="item">
          <h2>Header</h2>
          <p>Text</p>
        </div>
      </div>
    </div>
  </div>
</div>

What’s wrong with this code?

  • 🗑️ Garbage in DOM — extra nodes slow down rendering
  • 🎨 CSS hell — harder to style and debug
  • 📱 Semantic problems — screen readers get confused
  • 🐛 Debugging complexity — finding the right element becomes a quest

⚛️ React.Fragment — hero of our time

Fragment allows grouping elements without creating an extra DOM node.

Fragment syntax:

// Full notation
import React, { Fragment } from 'react';
 
function MyComponent() {
  return (
    <Fragment>
      <h1>Header</h1>
      <p>Paragraph</p>
    </Fragment>
  );
}
 
// Short notation
function MyComponent() {
  return (
    <>
      <h1>Header</h1>
      <p>Paragraph</p>
    </>
  );
}
 
// With key (full notation only)
function MyList({ items }) {
  return (
    <>
      {items.map(item => (
        <Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.definition}</dd>
        </Fragment>
      ))}
    </>
  );
}

✅ When Fragment is REALLY needed

1. Returning multiple elements from a component

// ❌ Bad — unnecessary div
function UserInfo({ user }) {
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <span>{user.role}</span>
    </div>
  );
}
 
// ✅ Good — without unnecessary wrapper
function UserInfo({ user }) {
  return (
    <>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <span>{user.role}</span>
    </>
  );
}

Result in DOM:

<!-- With div -->
<div>
  <h2>John Doe</h2>
  <p>john@example.com</p>
  <span>Developer</span>
</div>
 
<!-- With Fragment -->
<h2>John Doe</h2>
<p>john@example.com</p>
<span>Developer</span>

2. Conditional rendering of multiple elements

// ❌ Bad — unnecessary div with condition
function ConditionalContent({ showDetails, user }) {
  return (
    <div className="user-card">
      <h3>{user.name}</h3>
      {showDetails && (
        <div> {/* Unnecessary wrapper! */}
          <p>Email: {user.email}</p>
          <p>Phone: {user.phone}</p>
          <p>Address: {user.address}</p>
        </div>
      )}
    </div>
  );
}
 
// ✅ Good — clean DOM
function ConditionalContent({ showDetails, user }) {
  return (
    <div className="user-card">
      <h3>{user.name}</h3>
      {showDetails && (
        <>
          <p>Email: {user.email}</p>
          <p>Phone: {user.phone}</p>
          <p>Address: {user.address}</p>
        </>
      )}
    </div>
  );
}

3. Lists with multiple elements per iteration

// ❌ Bad — unnecessary divs in&nbsp;list
function DefinitionList({ terms }) {
  return (
    <dl>
      {terms.map(term => (
        <div key={term.id}> {/* Semantically wrong! */}
          <dt>{term.word}</dt>
          <dd>{term.definition}</dd>
        </div>
      ))}
    </dl>
  );
}
 
// ✅ Good — semantically correct
function DefinitionList({ terms }) {
  return (
    <dl>
      {terms.map(term => (
        <Fragment key={term.id}>
          <dt>{term.word}</dt>
          <dd>{term.definition}</dd>
        </Fragment>
      ))}
    </dl>
  );
}

4. Tables with conditional rows

// ❌ Bad — breaks table structure
function TableRows({ items, showTotals }) {
  return (
    <>
      {items.map(item => (
        <tr key={item.id}>
          <td>{item.name}</td>
          <td>{item.price}</td>
        </tr>
      ))}
      {showTotals && (
        <div> {/* div inside table — nightmare! */}
          <tr>
            <td>Total:</td>
            <td>{calculateTotal(items)}</td>
          </tr>
          <tr>
            <td>Tax:</td>
            <td>{calculateTax(items)}</td>
          </tr>
        </div>
      )}
    </>
  );
}
 
// ✅ Good — correct table structure
function TableRows({ items, showTotals }) {
  return (
    <>
      {items.map(item => (
        <tr key={item.id}>
          <td>{item.name}</td>
          <td>{item.price}</td>
        </tr>
      ))}
      {showTotals && (
        <>
          <tr>
            <td>Total:</td>
            <td>{calculateTotal(items)}</td>
          </tr>
          <tr>
            <td>Tax:</td>
            <td>{calculateTax(items)}</td>
          </tr>
        </>
      )}
    </>
  );
}

❌ When Fragment is NOT needed

1. One element — one component

// ❌ Excess — Fragment for one element
function SingleButton({ onClick, children }) {
  return (
    <>
      <button onClick={onClick}>{children}</button>
    </>
  );
}
 
// ✅ Simple and&nbsp;clear
function SingleButton({ onClick, children }) {
  return <button onClick={onClick}>{children}</button>;
}

2. When div is semantically justified

// ✅ Here div is&nbsp;needed for styling
function Card({ title, content }) {
  return (
    <div className="card"> {/* Needed for CSS */}
      <h3 className="card-title">{title}</h3>
      <p className="card-content">{content}</p>
    </div>
  );
}
 
// ❌ Fragment is&nbsp;inappropriate here
function Card({ title, content }) {
  return (
    <> {/* How to&nbsp;style the&nbsp;card? */}
      <h3 className="card-title">{title}</h3>
      <p className="card-content">{content}</p>
    </>
  );
}

3. When event handlers are needed

// ✅ div is&nbsp;needed for click handling
function ClickableArea({ onAreaClick, children }) {
  return (
    <div onClick={onAreaClick} className="clickable-area">
      {children}
    </div>
  );
}
 
// ❌ Fragment cannot handle events
function ClickableArea({ onAreaClick, children }) {
  return (
    <> {/* onClick doesn't work! */}
      {children}
    </>
  );
}

🔍 Real example: form refactoring

Before (with unnecessary divs):

function ContactForm() {
  const [showAdvanced, setShowAdvanced] = useState(false);
  
  return (
    <form className="contact-form">
      <div> {/* Unnecessary div #1 */}
        <label>Name</label>
        <input type="text" name="name" />
      </div>
      
      <div> {/* Unnecessary div #2 */}
        <label>Email</label>
        <input type="email" name="email" />
      </div>
      
      {showAdvanced && (
        <div> {/* Unnecessary div #3 */}
          <div> {/* Unnecessary div #4 */}
            <label>Phone</label>
            <input type="tel" name="phone" />
          </div>
          <div> {/* Unnecessary div #5 */}
            <label>Company</label>
            <input type="text" name="company" />
          </div>
        </div>
      )}
      
      <div> {/* Unnecessary div #6 */}
        <button type="submit">Submit</button>
        <button type="button" onClick={() => setShowAdvanced(!showAdvanced)}>
          {showAdvanced ? 'Hide' : 'Show'} additional fields
        </button>
      </div>
    </form>
  );
}

After (with Fragment):

function ContactForm() {
  const [showAdvanced, setShowAdvanced] = useState(false);
  
  return (
    <form className="contact-form">
      <div className="field-group"> {/* Semantically justified */}
        <label>Name</label>
        <input type="text" name="name" />
      </div>
      
      <div className="field-group"> {/* Semantically justified */}
        <label>Email</label>
        <input type="email" name="email" />
      </div>
      
      {showAdvanced && (
        <> {/* Grouping without extra DOM */}
          <div className="field-group">
            <label>Phone</label>
            <input type="tel" name="phone" />
          </div>
          <div className="field-group">
            <label>Company</label>
            <input type="text" name="company" />
          </div>
        </>
      )}
      
      <div className="button-group"> {/* Semantically justified */}
        <button type="submit">Submit</button>
        <button type="button" onClick={() => setShowAdvanced(!showAdvanced)}>
          {showAdvanced ? 'Hide' : 'Show'} additional fields
        </button>
      </div>
    </form>
  );
}

📊 Performance impact

Test: 1000 components with div vs Fragment

// Component with div
function ItemWithDiv({ title, description }) {
  return (
    <div>
      <h4>{title}</h4>
      <p>{description}</p>
    </div>
  );
}
 
// Component with Fragment
function ItemWithFragment({ title, description }) {
  return (
    <>
      <h4>{title}</h4>
      <p>{description}</p>
    </>
  );
}

Results:

  • With div: 3000 DOM nodes (1000 div + 2000 elements)
  • With Fragment: 2000 DOM nodes (only useful elements)
  • Savings: 33% DOM nodes
  • Render time: 15-20% faster with Fragment

🎯 Practical rules

When to use Fragment:

  1. Returning multiple elements from component
  2. Conditional rendering of element groups
  3. Lists with multiple elements per iteration
  4. Tables with conditional rows/cells
  5. Semantic structures (dl, table, ul)

When NOT to use Fragment:

  1. One element — just return it
  2. Styles needed — use semantic container
  3. Event handlers — Fragment doesn’t support them
  4. Semantic grouping — section, article, div with class

Checklist before using Fragment:

  • Am I returning more than one element?
  • Is div really unnecessary?
  • No styles needed for the group?
  • No event handlers needed?
  • Will semantics not suffer?

🛠️ Control tools

ESLint rule for Fragment control:

{
  "rules": {
    "react/jsx-fragments": ["error", "syntax"],
    "react/jsx-no-useless-fragment": "error"
  }
}

Prettier configuration:

{
  "jsxBracketSameLine": false,
  "jsxSingleQuote": false
}

💡 Conclusion

React.Fragment is not just syntactic sugar. It is a tool for:

  • 🧹 Clean DOM without garbage wrappers
  • Better performance due to fewer nodes
  • 🎯 Semantic correctness of HTML
  • 🐛 Simplified debugging in DevTools

Remember: every div should be justified. If it doesn’t carry semantic load and isn’t needed for styles — use Fragment.

Golden rule: Don’t breed wrappers for the sake of wrappers. DOM should be clean, like your conscience.


Want more articles about React and frontend? Subscribe to EasyAdvice, bookmark the site and level up every day 💪