or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

accessibility.mdcomponents.mdindex.mdstate-management.mdstyling.md

state-management.mddocs/

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

```