Fragments and keys are essential React features for clean component structure and efficient rendering. Here's how to use them.
React Fragments#
1import { Fragment } from 'react';
2
3// Problem: Components must return single element
4function BadComponent() {
5 // Error: Adjacent JSX elements must be wrapped
6 return (
7 <h1>Title</h1>
8 <p>Content</p>
9 );
10}
11
12// Solution 1: Fragment syntax
13function GoodComponent() {
14 return (
15 <Fragment>
16 <h1>Title</h1>
17 <p>Content</p>
18 </Fragment>
19 );
20}
21
22// Solution 2: Short syntax (preferred)
23function BetterComponent() {
24 return (
25 <>
26 <h1>Title</h1>
27 <p>Content</p>
28 </>
29 );
30}Why Not Div Wrappers?#
1// Using div creates extra DOM nodes
2function TableRow({ items }) {
3 return (
4 <div> {/* This breaks table structure */}
5 <td>{items.name}</td>
6 <td>{items.value}</td>
7 </div>
8 );
9}
10
11// Invalid HTML result:
12// <table>
13// <tr>
14// <div> <-- Invalid!
15// <td>...</td>
16// </div>
17// </tr>
18// </table>
19
20// Fragment solves this
21function TableRow({ items }) {
22 return (
23 <>
24 <td>{items.name}</td>
25 <td>{items.value}</td>
26 </>
27 );
28}
29
30// Valid HTML result:
31// <table>
32// <tr>
33// <td>...</td>
34// <td>...</td>
35// </tr>
36// </table>Fragments with Keys#
1// Short syntax doesn't support keys
2// Use Fragment when you need keys
3
4function Glossary({ items }) {
5 return (
6 <dl>
7 {items.map(item => (
8 // Must use Fragment, not <>
9 <Fragment key={item.id}>
10 <dt>{item.term}</dt>
11 <dd>{item.description}</dd>
12 </Fragment>
13 ))}
14 </dl>
15 );
16}
17
18// Another example
19function Comments({ comments }) {
20 return (
21 <>
22 {comments.map(comment => (
23 <Fragment key={comment.id}>
24 <Comment data={comment} />
25 <Divider />
26 </Fragment>
27 ))}
28 </>
29 );
30}Understanding Keys#
1// Keys help React identify which items changed
2function TodoList({ todos }) {
3 return (
4 <ul>
5 {todos.map(todo => (
6 <li key={todo.id}>{todo.text}</li>
7 ))}
8 </ul>
9 );
10}
11
12// Keys should be:
13// 1. Unique among siblings
14// 2. Stable (don't change between renders)
15// 3. Predictable (same input = same key)Good vs Bad Keys#
1// BAD: Using index as key
2function BadList({ items }) {
3 return (
4 <ul>
5 {items.map((item, index) => (
6 <li key={index}>{item.name}</li>
7 ))}
8 </ul>
9 );
10}
11// Problems:
12// - Reordering breaks state
13// - Insertions at start break state
14// - Deletions cause incorrect updates
15
16// GOOD: Using unique ID
17function GoodList({ items }) {
18 return (
19 <ul>
20 {items.map(item => (
21 <li key={item.id}>{item.name}</li>
22 ))}
23 </ul>
24 );
25}
26
27// GOOD: Using composite key
28function UserPosts({ users }) {
29 return (
30 <div>
31 {users.map(user =>
32 user.posts.map(post => (
33 <Post key={`${user.id}-${post.id}`} post={post} />
34 ))
35 )}
36 </div>
37 );
38}When Index Keys Are Okay#
1// Index keys are acceptable when:
2// 1. List is static (never reordered)
3// 2. Items have no stable IDs
4// 3. List is never filtered/sorted
5
6function StaticList() {
7 const items = ['Apple', 'Banana', 'Cherry'];
8
9 return (
10 <ul>
11 {items.map((item, index) => (
12 <li key={index}>{item}</li>
13 ))}
14 </ul>
15 );
16}
17
18// But add unique keys for dynamic lists
19let nextId = 0;
20
21function DynamicList() {
22 const [items, setItems] = useState([]);
23
24 const addItem = (name) => {
25 setItems([...items, { id: nextId++, name }]);
26 };
27
28 return (
29 <ul>
30 {items.map(item => (
31 <li key={item.id}>{item.name}</li>
32 ))}
33 </ul>
34 );
35}Key Behavior Demonstration#
1// Without proper keys - state gets confused
2function InputList() {
3 const [items, setItems] = useState([
4 { id: 1, text: 'First' },
5 { id: 2, text: 'Second' },
6 ]);
7
8 const addToStart = () => {
9 setItems([
10 { id: Date.now(), text: 'New' },
11 ...items
12 ]);
13 };
14
15 return (
16 <div>
17 <button onClick={addToStart}>Add to Start</button>
18 {items.map((item, index) => (
19 // BAD: Using index - input values get mixed up
20 <input key={index} defaultValue={item.text} />
21 ))}
22 </div>
23 );
24}
25
26// With proper keys - state is preserved correctly
27function CorrectInputList() {
28 const [items, setItems] = useState([
29 { id: 1, text: 'First' },
30 { id: 2, text: 'Second' },
31 ]);
32
33 return (
34 <div>
35 {items.map(item => (
36 // GOOD: Using unique ID
37 <input key={item.id} defaultValue={item.text} />
38 ))}
39 </div>
40 );
41}Resetting Components with Keys#
1// Key change = component remount
2function ProfilePage({ userId }) {
3 // When userId changes, entire form resets
4 return (
5 <ProfileForm key={userId} userId={userId} />
6 );
7}
8
9// Without key, form would keep old state
10function ProfileFormWithoutKey({ userId }) {
11 const [name, setName] = useState('');
12
13 useEffect(() => {
14 // Would need to manually reset
15 fetchUser(userId).then(user => setName(user.name));
16 }, [userId]);
17
18 return <input value={name} onChange={e => setName(e.target.value)} />;
19}
20
21// With key, form resets automatically
22function App() {
23 const [selectedUserId, setSelectedUserId] = useState(1);
24
25 return (
26 <div>
27 <UserList onSelect={setSelectedUserId} />
28 <EditForm key={selectedUserId} userId={selectedUserId} />
29 </div>
30 );
31}Generating Keys#
1// Using crypto.randomUUID (modern browsers)
2function addItem(text) {
3 const newItem = {
4 id: crypto.randomUUID(),
5 text
6 };
7 setItems([...items, newItem]);
8}
9
10// Using counter (simple approach)
11let idCounter = 0;
12function generateId() {
13 return ++idCounter;
14}
15
16// Using nanoid library
17import { nanoid } from 'nanoid';
18
19function addItem(text) {
20 setItems([...items, { id: nanoid(), text }]);
21}
22
23// From database
24// Usually items from backend have IDs
25const users = await fetchUsers();
26// users already have user.idFragments in Conditional Rendering#
1function ConditionalContent({ showExtra }) {
2 return (
3 <div>
4 <h1>Main Content</h1>
5
6 {showExtra && (
7 <>
8 <p>Extra paragraph one</p>
9 <p>Extra paragraph two</p>
10 </>
11 )}
12
13 <footer>Footer</footer>
14 </div>
15 );
16}
17
18// Multiple conditions
19function StatusDisplay({ status }) {
20 return (
21 <div>
22 {status === 'loading' && (
23 <>
24 <Spinner />
25 <p>Loading...</p>
26 </>
27 )}
28
29 {status === 'error' && (
30 <>
31 <ErrorIcon />
32 <p>Something went wrong</p>
33 <RetryButton />
34 </>
35 )}
36
37 {status === 'success' && (
38 <>
39 <SuccessIcon />
40 <p>Done!</p>
41 </>
42 )}
43 </div>
44 );
45}Nested Fragments#
1function NestedStructure() {
2 return (
3 <>
4 <Header />
5 <>
6 <Sidebar />
7 <Main>
8 <>
9 <Article />
10 <Comments />
11 </>
12 </Main>
13 </>
14 <Footer />
15 </>
16 );
17}
18
19// Usually better to use components
20function BetterStructure() {
21 return (
22 <>
23 <Header />
24 <Layout>
25 <Content />
26 </Layout>
27 <Footer />
28 </>
29 );
30}Common Patterns#
1// Return array from render
2function ReturnArray() {
3 // Can return array with keys
4 return [
5 <li key="a">Item A</li>,
6 <li key="b">Item B</li>,
7 <li key="c">Item C</li>
8 ];
9}
10
11// Map with fragment wrapper
12function WrapperPattern({ items }) {
13 return (
14 <>
15 {items.map(item => (
16 <Fragment key={item.id}>
17 <ListItem item={item} />
18 {item.isLast || <Divider />}
19 </Fragment>
20 ))}
21 </>
22 );
23}
24
25// Fragment in higher-order component
26function withWrapper(Component) {
27 return function Wrapped(props) {
28 return (
29 <>
30 <Component {...props} />
31 <Analytics />
32 </>
33 );
34 };
35}Best Practices#
Fragments:
✓ Use <> for simple grouping
✓ Use <Fragment> when keys needed
✓ Avoid unnecessary wrapper divs
✓ Keep DOM structure clean
Keys:
✓ Use unique, stable IDs
✓ Use database IDs when available
✓ Generate IDs at creation time
✓ Use key to force remount
Avoid:
✗ Index as key for dynamic lists
✗ Random keys on each render
✗ Non-unique keys among siblings
✗ Unnecessary Fragment nesting
Performance:
✓ Keys enable efficient diffing
✓ Stable keys prevent remounting
✓ Fragments reduce DOM nodes
✓ Proper keys preserve state
Conclusion#
Fragments let you group elements without adding extra DOM nodes - use the short syntax <> when possible, and <Fragment> when you need keys. Keys help React efficiently update lists - always use unique, stable identifiers. Avoid index keys for dynamic lists, and remember that changing a key forces a component to remount, which can be useful for resetting state.