Fragments let you group elements without adding extra DOM nodes. Here's how to use them.
Basic Fragment Syntax#
1import { Fragment } from 'react';
2
3// Full syntax
4function Columns() {
5 return (
6 <Fragment>
7 <td>Hello</td>
8 <td>World</td>
9 </Fragment>
10 );
11}
12
13// Short syntax
14function ColumnsShort() {
15 return (
16 <>
17 <td>Hello</td>
18 <td>World</td>
19 </>
20 );
21}
22
23// Why not div?
24function Table() {
25 return (
26 <table>
27 <tbody>
28 <tr>
29 {/* Using div here would be invalid HTML */}
30 <Columns />
31 </tr>
32 </tbody>
33 </table>
34 );
35}When to Use Fragments#
1// Returning multiple elements
2function UserInfo({ user }) {
3 return (
4 <>
5 <dt>Name</dt>
6 <dd>{user.name}</dd>
7 <dt>Email</dt>
8 <dd>{user.email}</dd>
9 </>
10 );
11}
12
13function DefinitionList({ users }) {
14 return (
15 <dl>
16 {users.map(user => (
17 <UserInfo key={user.id} user={user} />
18 ))}
19 </dl>
20 );
21}
22
23// Conditional rendering
24function Content({ isLoggedIn }) {
25 return (
26 <>
27 {isLoggedIn ? (
28 <>
29 <Header />
30 <Dashboard />
31 <Footer />
32 </>
33 ) : (
34 <>
35 <Header />
36 <LoginForm />
37 </>
38 )}
39 </>
40 );
41}
42
43// Avoiding wrapper div
44function Navigation() {
45 return (
46 <nav>
47 <>
48 <Link to="/">Home</Link>
49 <Link to="/about">About</Link>
50 <Link to="/contact">Contact</Link>
51 </>
52 </nav>
53 );
54}Keyed Fragments#
1import { Fragment } from 'react';
2
3// With keys (can't use short syntax)
4function GlossaryList({ items }) {
5 return (
6 <dl>
7 {items.map(item => (
8 <Fragment key={item.id}>
9 <dt>{item.term}</dt>
10 <dd>{item.definition}</dd>
11 </Fragment>
12 ))}
13 </dl>
14 );
15}
16
17// Table rows with multiple cells
18function DataRows({ data }) {
19 return (
20 <>
21 {data.map(row => (
22 <Fragment key={row.id}>
23 <tr className="main-row">
24 <td>{row.name}</td>
25 <td>{row.value}</td>
26 </tr>
27 <tr className="detail-row">
28 <td colSpan={2}>{row.details}</td>
29 </tr>
30 </Fragment>
31 ))}
32 </>
33 );
34}
35
36// Short syntax doesn't support keys
37// This won't work:
38// <> key={item.id} // Error!Avoiding Extra Wrappers#
1// Problem: Extra wrapper div affects layout
2function BadLayout() {
3 return (
4 <div className="flex-container">
5 <FlexItems /> {/* Adds extra div, breaks flexbox */}
6 </div>
7 );
8}
9
10function FlexItemsBad() {
11 return (
12 <div> {/* This extra div breaks the flex layout */}
13 <div className="flex-item">Item 1</div>
14 <div className="flex-item">Item 2</div>
15 </div>
16 );
17}
18
19// Solution: Use Fragment
20function GoodLayout() {
21 return (
22 <div className="flex-container">
23 <FlexItems /> {/* No extra wrapper */}
24 </div>
25 );
26}
27
28function FlexItems() {
29 return (
30 <>
31 <div className="flex-item">Item 1</div>
32 <div className="flex-item">Item 2</div>
33 </>
34 );
35}
36
37// Grid layout
38function GridItems() {
39 return (
40 <>
41 <div className="grid-item">1</div>
42 <div className="grid-item">2</div>
43 <div className="grid-item">3</div>
44 </>
45 );
46}With Arrays#
1// Mapping to fragments
2function TagList({ tags }) {
3 return (
4 <div>
5 {tags.map((tag, index) => (
6 <Fragment key={tag}>
7 {index > 0 && ', '}
8 <span className="tag">{tag}</span>
9 </Fragment>
10 ))}
11 </div>
12 );
13}
14
15// Result: Tag1, Tag2, Tag3
16
17// Alternative: join with elements
18function TagListAlt({ tags }) {
19 return (
20 <div>
21 {tags
22 .map(tag => <span className="tag">{tag}</span>)
23 .reduce((prev, curr, i) => (
24 <Fragment key={i}>
25 {prev}
26 {i > 0 && ', '}
27 {curr}
28 </Fragment>
29 ))}
30 </div>
31 );
32}Conditional Fragments#
1// Conditional content groups
2function UserProfile({ user, isOwner }) {
3 return (
4 <div className="profile">
5 <h1>{user.name}</h1>
6
7 {isOwner && (
8 <>
9 <button>Edit Profile</button>
10 <button>Settings</button>
11 </>
12 )}
13
14 {!isOwner && (
15 <>
16 <button>Follow</button>
17 <button>Message</button>
18 </>
19 )}
20 </div>
21 );
22}
23
24// With ternary
25function StatusBadge({ status }) {
26 return (
27 <div className="status">
28 {status === 'active' ? (
29 <>
30 <span className="dot green" />
31 <span>Active</span>
32 </>
33 ) : (
34 <>
35 <span className="dot gray" />
36 <span>Inactive</span>
37 </>
38 )}
39 </div>
40 );
41}Component Composition#
1// Higher-order component returning fragment
2function withLogging(WrappedComponent) {
3 return function LoggedComponent(props) {
4 useEffect(() => {
5 console.log('Component rendered');
6 });
7
8 return (
9 <>
10 <WrappedComponent {...props} />
11 {process.env.NODE_ENV === 'development' && (
12 <div className="debug-info">Debug Mode</div>
13 )}
14 </>
15 );
16 };
17}
18
19// Render prop with fragment
20function Toggle({ children }) {
21 const [isOn, setIsOn] = useState(false);
22
23 return children({
24 isOn,
25 toggle: () => setIsOn(!isOn),
26 });
27}
28
29function App() {
30 return (
31 <Toggle>
32 {({ isOn, toggle }) => (
33 <>
34 <button onClick={toggle}>Toggle</button>
35 {isOn && <div>Content is visible!</div>}
36 </>
37 )}
38 </Toggle>
39 );
40}Form Elements#
1// Form field groups
2function FormFields() {
3 return (
4 <>
5 <label htmlFor="email">Email</label>
6 <input id="email" type="email" />
7
8 <label htmlFor="password">Password</label>
9 <input id="password" type="password" />
10 </>
11 );
12}
13
14function LoginForm() {
15 return (
16 <form>
17 <FormFields />
18 <button type="submit">Login</button>
19 </form>
20 );
21}
22
23// Fieldset contents
24function AddressFields() {
25 return (
26 <>
27 <div className="field">
28 <label>Street</label>
29 <input name="street" />
30 </div>
31 <div className="field">
32 <label>City</label>
33 <input name="city" />
34 </div>
35 <div className="field">
36 <label>ZIP</label>
37 <input name="zip" />
38 </div>
39 </>
40 );
41}Performance Considerations#
1// Fragments don't create DOM nodes
2// No performance overhead from wrapper elements
3
4// Before (extra div)
5<div> {/* Creates DOM node */}
6 <Child1 />
7 <Child2 />
8</div>
9
10// After (Fragment)
11<> {/* No DOM node */}
12 <Child1 />
13 <Child2 />
14</>
15
16// DOM output is cleaner
17// <div id="root">
18// <Child1 />
19// <Child2 />
20// </div>
21
22// vs
23
24// <div id="root">
25// <div> <!-- Extra wrapper -->
26// <Child1 />
27// <Child2 />
28// </div>
29// </div>Common Mistakes#
1// Wrong: Key on short syntax
2function List({ items }) {
3 return items.map(item => (
4 // <> key={item.id}> // Syntax error!
5 <Fragment key={item.id}>
6 <span>{item.name}</span>
7 </Fragment>
8 ));
9}
10
11// Wrong: Attributes on short syntax
12function Wrong() {
13 return (
14 // <> className="wrapper"> // Can't add attributes!
15 <></>
16 );
17}
18
19// Wrong: Using div when Fragment would work
20function Unnecessary() {
21 return (
22 <div> {/* Why add this if you don't need it? */}
23 <Child1 />
24 <Child2 />
25 </div>
26 );
27}Best Practices#
When to Use:
✓ Returning multiple elements
✓ Avoiding layout-breaking wrappers
✓ Table rows with multiple cells
✓ Definition lists
Syntax Choice:
✓ Use <></> for simple cases
✓ Use <Fragment> when you need keys
✓ Import Fragment explicitly when needed
✓ Be consistent in your codebase
Avoid:
✗ Keys on short syntax (won't work)
✗ Attributes on short syntax
✗ Unnecessary wrapper divs
✗ Over-nesting fragments
Conclusion#
Fragments provide a clean way to group elements without adding extra DOM nodes. Use the short syntax for simple grouping and the full Fragment syntax when you need keys. They're especially useful for table rows, definition lists, and avoiding layout-breaking wrapper elements.