CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-tabs

An accessible and easy tab component for ReactJS with full keyboard navigation and ARIA support

Pending
Overview
Eval results
Files

state-management.mddocs/

State Management

Flexible state management supporting both controlled and uncontrolled modes with comprehensive event handling and validation.

Core Imports

import { Tabs } from "react-tabs";

Capabilities

Controlled vs Uncontrolled Mode

React Tabs supports two distinct modes for managing which tab is currently selected.

/**
 * Controlled mode: Parent component manages selectedIndex
 * Requires both selectedIndex and onSelect props
 */
interface ControlledTabsProps {
  selectedIndex: number;
  onSelect: (index: number, last: number, event: Event) => boolean | void;
}

/**
 * Uncontrolled mode: Component manages state internally
 * Uses defaultIndex for initial selection
 */
interface UncontrolledTabsProps {
  defaultIndex?: number; // Initial tab index (default: 0)
  selectedIndex?: null | undefined; // Must be null/undefined for uncontrolled
}

Controlled Mode Usage:

import { useState } from 'react';
import { Tabs, TabList, Tab, TabPanel } from 'react-tabs';

function ControlledExample() {
  const [tabIndex, setTabIndex] = useState(0);

  const handleTabSelect = (index, lastIndex, event) => {
    console.log(`Switching from tab ${lastIndex} to tab ${index}`);
    setTabIndex(index);
    // Return false to prevent the tab change
    // return false;
  };

  return (
    <Tabs selectedIndex={tabIndex} onSelect={handleTabSelect}>
      <TabList>
        <Tab>Tab 1</Tab>
        <Tab>Tab 2</Tab>
        <Tab>Tab 3</Tab>
      </TabList>
      <TabPanel>Content 1</TabPanel>
      <TabPanel>Content 2</TabPanel>
      <TabPanel>Content 3</TabPanel>
    </Tabs>
  );
}

Uncontrolled Mode Usage:

function UncontrolledExample() {
  const handleTabSelect = (index, lastIndex, event) => {
    console.log(`Tab changed from ${lastIndex} to ${index}`);
  };

  return (
    <Tabs defaultIndex={1} onSelect={handleTabSelect}>
      <TabList>
        <Tab>Tab 1</Tab>
        <Tab>Tab 2</Tab>
        <Tab>Tab 3</Tab>
      </TabList>
      <TabPanel>Content 1</TabPanel>
      <TabPanel>Content 2</TabPanel>
      <TabPanel>Content 3</TabPanel>
    </Tabs>
  );
}

Selection Events

Comprehensive event handling for tab selection changes with cancellation support.

/**
 * Tab selection event handler
 * @param index - The index of the newly selected tab
 * @param lastIndex - The index of the previously selected tab
 * @param event - The DOM event that triggered the selection
 * @returns boolean | void - Return false to prevent tab change
 */
type OnSelectHandler = (
  index: number, 
  lastIndex: number, 
  event: Event
) => boolean | void;

interface TabsProps {
  /** Called when tab selection changes */
  onSelect?: OnSelectHandler;
}

Event Handler Examples:

// Basic logging
const handleSelect = (index, lastIndex, event) => {
  console.log(`Switched from tab ${lastIndex} to tab ${index}`);
};

// Conditional prevention
const handleSelectWithValidation = (index, lastIndex, event) => {
  // Prevent switching if form is dirty
  if (hasUnsavedChanges && !confirm('You have unsaved changes. Continue?')) {
    return false; // Prevents the tab change
  }
  
  // Save current tab state
  saveTabState(lastIndex);
  loadTabState(index);
};

// Event type handling
const handleSelectByEventType = (index, lastIndex, event) => {
  if (event.type === 'click') {
    console.log('Tab selected by click');
  } else if (event.type === 'keydown') {
    console.log('Tab selected by keyboard');
  }
};

Default Configuration

Initial state configuration for uncontrolled mode with validation.

interface TabsProps {
  /** Initial tab index (0-based) for uncontrolled mode */
  defaultIndex?: number; // Default: 0
  /** Focus the selected tab on initial render */
  defaultFocus?: boolean; // Default: false
}

Configuration Examples:

// Start with second tab selected and focused
<Tabs defaultIndex={1} defaultFocus={true}>
  <TabList>
    <Tab>First</Tab>
    <Tab>Second (Initially Selected)</Tab>
    <Tab>Third</Tab>
  </TabList>
  <TabPanel>First Content</TabPanel>
  <TabPanel>Second Content</TabPanel>
  <TabPanel>Third Content</TabPanel>
</Tabs>

// Dynamic default index based on URL or props
const getInitialTab = () => {
  const hash = window.location.hash;
  if (hash === '#settings') return 2;
  if (hash === '#profile') return 1;
  return 0;
};

<Tabs defaultIndex={getInitialTab()}>
  {/* tabs and panels */}
</Tabs>

State Persistence

Advanced state management patterns for persisting tab state across sessions.

Local Storage Integration:

function PersistentTabs() {
  const [selectedTab, setSelectedTab] = useState(() => {
    const saved = localStorage.getItem('selectedTab');
    return saved ? parseInt(saved, 10) : 0;
  });

  const handleTabChange = (index) => {
    setSelectedTab(index);
    localStorage.setItem('selectedTab', index.toString());
  };

  return (
    <Tabs selectedIndex={selectedTab} onSelect={handleTabChange}>
      {/* tabs and panels */}
    </Tabs>
  );
}

URL Synchronization:

import { useNavigate, useLocation } from 'react-router-dom';

function URLSyncedTabs() {
  const navigate = useNavigate();
  const location = useLocation();
  
  const tabMap = ['overview', 'details', 'settings'];
  const currentTab = tabMap.indexOf(location.pathname.split('/').pop()) || 0;

  const handleTabChange = (index) => {
    const tabName = tabMap[index];
    navigate(`/dashboard/${tabName}`);
  };

  return (
    <Tabs selectedIndex={currentTab} onSelect={handleTabChange}>
      <TabList>
        <Tab>Overview</Tab>
        <Tab>Details</Tab>
        <Tab>Settings</Tab>
      </TabList>
      <TabPanel>Overview Content</TabPanel>
      <TabPanel>Details Content</TabPanel>
      <TabPanel>Settings Content</TabPanel>
    </Tabs>
  );
}

Validation and Error Handling

Built-in validation ensures proper component structure and prevents common errors.

/**
 * Validation errors that may be thrown (development mode only):
 * - Error: "Switching between controlled mode... and uncontrolled mode is not supported"
 * - Error: Tab and TabPanel counts must match (enforced via React warnings)
 * - Error: Only one TabList component allowed per Tabs container
 */
interface ValidationBehavior {
  /** Validates equal number of Tab and TabPanel components */
  validateStructure: boolean;
  /** Prevents mode switching after initial render */
  preventModeChange: boolean;
  /** Ensures proper component nesting */
  validateNesting: boolean;
}

Common Validation Errors:

// ❌ This will throw an error - switching modes
function BrokenModeSwitch() {
  const [isControlled, setIsControlled] = useState(false);
  
  return (
    <Tabs 
      selectedIndex={isControlled ? 0 : null} // Error: Cannot switch between controlled/uncontrolled
      defaultIndex={isControlled ? null : 0}
    >
      {/* components */}
    </Tabs>
  );
}

// ❌ Another mode switching error
function AnotherBrokenExample() {
  const [selectedIndex, setSelectedIndex] = useState(null);
  
  // Later in the component lifecycle...
  useEffect(() => {
    setSelectedIndex(0); // Error: Cannot switch from uncontrolled to controlled
  }, []);
  
  return (
    <Tabs selectedIndex={selectedIndex}>
      {/* components */}
    </Tabs>
  );
}

// ❌ This will throw an error - mismatched counts
<Tabs>
  <TabList>
    <Tab>Tab 1</Tab>
    <Tab>Tab 2</Tab>
  </TabList>
  <TabPanel>Panel 1</TabPanel>
  {/* Missing second TabPanel */}
</Tabs>

// ✅ Correct usage
<Tabs>
  <TabList>
    <Tab>Tab 1</Tab>
    <Tab>Tab 2</Tab>
  </TabList>
  <TabPanel>Panel 1</TabPanel>
  <TabPanel>Panel 2</TabPanel>
</Tabs>

Mode Detection

Internal logic for determining and maintaining tab management mode.

/**
 * Mode detection based on props
 * @param selectedIndex - The selectedIndex prop value  
 * @returns 'controlled' | 'uncontrolled'
 */
type TabMode = 'controlled' | 'uncontrolled';

// Internal mode detection logic:
// - selectedIndex === null → uncontrolled mode
// - selectedIndex is number → controlled mode

The library automatically detects the intended mode based on props and maintains consistency throughout the component lifecycle.

Runtime Validation

Built-in prop validation in development mode ensures correct usage patterns.

/**
 * Runtime validation features (development mode only):
 * - Prop type validation using checkPropTypes
 * - Component structure validation  
 * - Mode consistency checking
 * - Tab/Panel count matching
 */
interface ValidationFeatures {
  /** Validates prop types in development */
  propTypeChecking: boolean;
  /** Ensures equal Tab/TabPanel counts */
  structureValidation: boolean;
  /** Prevents controlled/uncontrolled mode switching */
  modeConsistency: boolean;
  /** Only active in NODE_ENV !== 'production' */
  developmentOnly: boolean;
}

Validation Examples:

// Development mode validation
if (process.env.NODE_ENV !== 'production') {
  // These errors will be thrown in development:
  
  // ❌ Mode switching error
  <Tabs selectedIndex={someCondition ? 0 : null}>
    {/* Error: Cannot switch between controlled/uncontrolled */}
  </Tabs>
  
  // ❌ Structure validation error  
  <Tabs>
    <TabList>
      <Tab>Tab 1</Tab>
      <Tab>Tab 2</Tab>
    </TabList>
    <TabPanel>Panel 1</TabPanel>
    {/* Error: Unequal Tab/TabPanel count */}
  </Tabs>
}

// ✅ Error boundary for validation errors
function TabsWithErrorBoundary() {
  return (
    <ErrorBoundary fallback={<div>Tab configuration error</div>}>
      <Tabs>
        <TabList>
          <Tab>Tab 1</Tab>
          <Tab>Tab 2</Tab>
        </TabList>
        <TabPanel>Panel 1</TabPanel>
        <TabPanel>Panel 2</TabPanel>
      </Tabs>
    </ErrorBoundary>
  );
}

Install with Tessl CLI

npx tessl i tessl/npm-react-tabs

docs

accessibility.md

components.md

index.md

state-management.md

styling.md

tile.json