0
# State Management
1
2
Flexible state management supporting both controlled and uncontrolled modes with comprehensive event handling and validation.
3
4
## Core Imports
5
6
```javascript
7
import { Tabs } from "react-tabs";
8
```
9
10
## Capabilities
11
12
### Controlled vs Uncontrolled Mode
13
14
React Tabs supports two distinct modes for managing which tab is currently selected.
15
16
```typescript { .api }
17
/**
18
* Controlled mode: Parent component manages selectedIndex
19
* Requires both selectedIndex and onSelect props
20
*/
21
interface ControlledTabsProps {
22
selectedIndex: number;
23
onSelect: (index: number, last: number, event: Event) => boolean | void;
24
}
25
26
/**
27
* Uncontrolled mode: Component manages state internally
28
* Uses defaultIndex for initial selection
29
*/
30
interface UncontrolledTabsProps {
31
defaultIndex?: number; // Initial tab index (default: 0)
32
selectedIndex?: null | undefined; // Must be null/undefined for uncontrolled
33
}
34
```
35
36
**Controlled Mode Usage:**
37
38
```javascript
39
import { useState } from 'react';
40
import { Tabs, TabList, Tab, TabPanel } from 'react-tabs';
41
42
function ControlledExample() {
43
const [tabIndex, setTabIndex] = useState(0);
44
45
const handleTabSelect = (index, lastIndex, event) => {
46
console.log(`Switching from tab ${lastIndex} to tab ${index}`);
47
setTabIndex(index);
48
// Return false to prevent the tab change
49
// return false;
50
};
51
52
return (
53
<Tabs selectedIndex={tabIndex} onSelect={handleTabSelect}>
54
<TabList>
55
<Tab>Tab 1</Tab>
56
<Tab>Tab 2</Tab>
57
<Tab>Tab 3</Tab>
58
</TabList>
59
<TabPanel>Content 1</TabPanel>
60
<TabPanel>Content 2</TabPanel>
61
<TabPanel>Content 3</TabPanel>
62
</Tabs>
63
);
64
}
65
```
66
67
**Uncontrolled Mode Usage:**
68
69
```javascript
70
function UncontrolledExample() {
71
const handleTabSelect = (index, lastIndex, event) => {
72
console.log(`Tab changed from ${lastIndex} to ${index}`);
73
};
74
75
return (
76
<Tabs defaultIndex={1} onSelect={handleTabSelect}>
77
<TabList>
78
<Tab>Tab 1</Tab>
79
<Tab>Tab 2</Tab>
80
<Tab>Tab 3</Tab>
81
</TabList>
82
<TabPanel>Content 1</TabPanel>
83
<TabPanel>Content 2</TabPanel>
84
<TabPanel>Content 3</TabPanel>
85
</Tabs>
86
);
87
}
88
```
89
90
### Selection Events
91
92
Comprehensive event handling for tab selection changes with cancellation support.
93
94
```typescript { .api }
95
/**
96
* Tab selection event handler
97
* @param index - The index of the newly selected tab
98
* @param lastIndex - The index of the previously selected tab
99
* @param event - The DOM event that triggered the selection
100
* @returns boolean | void - Return false to prevent tab change
101
*/
102
type OnSelectHandler = (
103
index: number,
104
lastIndex: number,
105
event: Event
106
) => boolean | void;
107
108
interface TabsProps {
109
/** Called when tab selection changes */
110
onSelect?: OnSelectHandler;
111
}
112
```
113
114
**Event Handler Examples:**
115
116
```javascript
117
// Basic logging
118
const handleSelect = (index, lastIndex, event) => {
119
console.log(`Switched from tab ${lastIndex} to tab ${index}`);
120
};
121
122
// Conditional prevention
123
const handleSelectWithValidation = (index, lastIndex, event) => {
124
// Prevent switching if form is dirty
125
if (hasUnsavedChanges && !confirm('You have unsaved changes. Continue?')) {
126
return false; // Prevents the tab change
127
}
128
129
// Save current tab state
130
saveTabState(lastIndex);
131
loadTabState(index);
132
};
133
134
// Event type handling
135
const handleSelectByEventType = (index, lastIndex, event) => {
136
if (event.type === 'click') {
137
console.log('Tab selected by click');
138
} else if (event.type === 'keydown') {
139
console.log('Tab selected by keyboard');
140
}
141
};
142
```
143
144
### Default Configuration
145
146
Initial state configuration for uncontrolled mode with validation.
147
148
```typescript { .api }
149
interface TabsProps {
150
/** Initial tab index (0-based) for uncontrolled mode */
151
defaultIndex?: number; // Default: 0
152
/** Focus the selected tab on initial render */
153
defaultFocus?: boolean; // Default: false
154
}
155
```
156
157
**Configuration Examples:**
158
159
```javascript
160
// Start with second tab selected and focused
161
<Tabs defaultIndex={1} defaultFocus={true}>
162
<TabList>
163
<Tab>First</Tab>
164
<Tab>Second (Initially Selected)</Tab>
165
<Tab>Third</Tab>
166
</TabList>
167
<TabPanel>First Content</TabPanel>
168
<TabPanel>Second Content</TabPanel>
169
<TabPanel>Third Content</TabPanel>
170
</Tabs>
171
172
// Dynamic default index based on URL or props
173
const getInitialTab = () => {
174
const hash = window.location.hash;
175
if (hash === '#settings') return 2;
176
if (hash === '#profile') return 1;
177
return 0;
178
};
179
180
<Tabs defaultIndex={getInitialTab()}>
181
{/* tabs and panels */}
182
</Tabs>
183
```
184
185
### State Persistence
186
187
Advanced state management patterns for persisting tab state across sessions.
188
189
**Local Storage Integration:**
190
191
```javascript
192
function PersistentTabs() {
193
const [selectedTab, setSelectedTab] = useState(() => {
194
const saved = localStorage.getItem('selectedTab');
195
return saved ? parseInt(saved, 10) : 0;
196
});
197
198
const handleTabChange = (index) => {
199
setSelectedTab(index);
200
localStorage.setItem('selectedTab', index.toString());
201
};
202
203
return (
204
<Tabs selectedIndex={selectedTab} onSelect={handleTabChange}>
205
{/* tabs and panels */}
206
</Tabs>
207
);
208
}
209
```
210
211
**URL Synchronization:**
212
213
```javascript
214
import { useNavigate, useLocation } from 'react-router-dom';
215
216
function URLSyncedTabs() {
217
const navigate = useNavigate();
218
const location = useLocation();
219
220
const tabMap = ['overview', 'details', 'settings'];
221
const currentTab = tabMap.indexOf(location.pathname.split('/').pop()) || 0;
222
223
const handleTabChange = (index) => {
224
const tabName = tabMap[index];
225
navigate(`/dashboard/${tabName}`);
226
};
227
228
return (
229
<Tabs selectedIndex={currentTab} onSelect={handleTabChange}>
230
<TabList>
231
<Tab>Overview</Tab>
232
<Tab>Details</Tab>
233
<Tab>Settings</Tab>
234
</TabList>
235
<TabPanel>Overview Content</TabPanel>
236
<TabPanel>Details Content</TabPanel>
237
<TabPanel>Settings Content</TabPanel>
238
</Tabs>
239
);
240
}
241
```
242
243
### Validation and Error Handling
244
245
Built-in validation ensures proper component structure and prevents common errors.
246
247
```typescript { .api }
248
/**
249
* Validation errors that may be thrown (development mode only):
250
* - Error: "Switching between controlled mode... and uncontrolled mode is not supported"
251
* - Error: Tab and TabPanel counts must match (enforced via React warnings)
252
* - Error: Only one TabList component allowed per Tabs container
253
*/
254
interface ValidationBehavior {
255
/** Validates equal number of Tab and TabPanel components */
256
validateStructure: boolean;
257
/** Prevents mode switching after initial render */
258
preventModeChange: boolean;
259
/** Ensures proper component nesting */
260
validateNesting: boolean;
261
}
262
```
263
264
**Common Validation Errors:**
265
266
```javascript
267
// ❌ This will throw an error - switching modes
268
function BrokenModeSwitch() {
269
const [isControlled, setIsControlled] = useState(false);
270
271
return (
272
<Tabs
273
selectedIndex={isControlled ? 0 : null} // Error: Cannot switch between controlled/uncontrolled
274
defaultIndex={isControlled ? null : 0}
275
>
276
{/* components */}
277
</Tabs>
278
);
279
}
280
281
// ❌ Another mode switching error
282
function AnotherBrokenExample() {
283
const [selectedIndex, setSelectedIndex] = useState(null);
284
285
// Later in the component lifecycle...
286
useEffect(() => {
287
setSelectedIndex(0); // Error: Cannot switch from uncontrolled to controlled
288
}, []);
289
290
return (
291
<Tabs selectedIndex={selectedIndex}>
292
{/* components */}
293
</Tabs>
294
);
295
}
296
297
// ❌ This will throw an error - mismatched counts
298
<Tabs>
299
<TabList>
300
<Tab>Tab 1</Tab>
301
<Tab>Tab 2</Tab>
302
</TabList>
303
<TabPanel>Panel 1</TabPanel>
304
{/* Missing second TabPanel */}
305
</Tabs>
306
307
// ✅ Correct usage
308
<Tabs>
309
<TabList>
310
<Tab>Tab 1</Tab>
311
<Tab>Tab 2</Tab>
312
</TabList>
313
<TabPanel>Panel 1</TabPanel>
314
<TabPanel>Panel 2</TabPanel>
315
</Tabs>
316
```
317
318
### Mode Detection
319
320
Internal logic for determining and maintaining tab management mode.
321
322
```typescript { .api }
323
/**
324
* Mode detection based on props
325
* @param selectedIndex - The selectedIndex prop value
326
* @returns 'controlled' | 'uncontrolled'
327
*/
328
type TabMode = 'controlled' | 'uncontrolled';
329
330
// Internal mode detection logic:
331
// - selectedIndex === null → uncontrolled mode
332
// - selectedIndex is number → controlled mode
333
```
334
335
The library automatically detects the intended mode based on props and maintains consistency throughout the component lifecycle.
336
337
### Runtime Validation
338
339
Built-in prop validation in development mode ensures correct usage patterns.
340
341
```typescript { .api }
342
/**
343
* Runtime validation features (development mode only):
344
* - Prop type validation using checkPropTypes
345
* - Component structure validation
346
* - Mode consistency checking
347
* - Tab/Panel count matching
348
*/
349
interface ValidationFeatures {
350
/** Validates prop types in development */
351
propTypeChecking: boolean;
352
/** Ensures equal Tab/TabPanel counts */
353
structureValidation: boolean;
354
/** Prevents controlled/uncontrolled mode switching */
355
modeConsistency: boolean;
356
/** Only active in NODE_ENV !== 'production' */
357
developmentOnly: boolean;
358
}
359
```
360
361
**Validation Examples:**
362
363
```javascript
364
// Development mode validation
365
if (process.env.NODE_ENV !== 'production') {
366
// These errors will be thrown in development:
367
368
// ❌ Mode switching error
369
<Tabs selectedIndex={someCondition ? 0 : null}>
370
{/* Error: Cannot switch between controlled/uncontrolled */}
371
</Tabs>
372
373
// ❌ Structure validation error
374
<Tabs>
375
<TabList>
376
<Tab>Tab 1</Tab>
377
<Tab>Tab 2</Tab>
378
</TabList>
379
<TabPanel>Panel 1</TabPanel>
380
{/* Error: Unequal Tab/TabPanel count */}
381
</Tabs>
382
}
383
384
// ✅ Error boundary for validation errors
385
function TabsWithErrorBoundary() {
386
return (
387
<ErrorBoundary fallback={<div>Tab configuration error</div>}>
388
<Tabs>
389
<TabList>
390
<Tab>Tab 1</Tab>
391
<Tab>Tab 2</Tab>
392
</TabList>
393
<TabPanel>Panel 1</TabPanel>
394
<TabPanel>Panel 2</TabPanel>
395
</Tabs>
396
</ErrorBoundary>
397
);
398
}
399
```