Nested States

Nested states allow you to create hierarchical menu structures and sub-menus within your USSD application. This enables complex navigation flows while maintaining clean, organized code.

Understanding Nested States

Nested states are states that exist within a parent state's flow. They allow you to:

  • Create sub-menus and multi-level navigation
  • Organize related functionality into logical groups
  • Implement "back" navigation to return to parent states
  • Maintain context and data across nested flows

Basic Nested State Example

Let's create a banking application with nested states for account management:

Main Menu State (Parent)

<?php

namespace App\Ussd\States;

use Vendor\LaravelUssd\Support\AbstractState;
use Vendor\LaravelUssd\Support\Context;
use Vendor\LaravelUssd\Menu\Menu;

class MainMenuState extends AbstractState
{
    protected function buildMenu(Context $context): Menu
    {
        return (new Menu())
            ->text('Main Menu')
            ->line('')
            ->option('1', 'Account Services')
            ->option('2', 'Transfer Money')
            ->option('3', 'Bill Payments')
            ->option('0', 'Exit')
            ->expectsInput();
    }

    public function next(Context $context, string $input): ?string
    {
        $this->setContext($context);
        
        return $this->decision($input)
            ->equal('1', AccountServicesState::class) // Nested state
            ->equal('2', TransferState::class)
            ->equal('3', BillPaymentsState::class)
            ->equal('0', null) // End session
            ->any(MainMenuState::class);
    }
}

Account Services State (Nested)

<?php

namespace App\Ussd\States;

use Vendor\LaravelUssd\Support\AbstractState;
use Vendor\LaravelUssd\Support\Context;
use Vendor\LaravelUssd\Menu\Menu;

class AccountServicesState extends AbstractState
{
    protected function buildMenu(Context $context): Menu
    {
        return (new Menu())
            ->text('Account Services')
            ->line('')
            ->option('1', 'Check Balance')
            ->option('2', 'Mini Statement')
            ->option('3', 'Account Details')
            ->option('0', 'Back to Main Menu')
            ->expectsInput();
    }

    public function next(Context $context, string $input): ?string
    {
        $this->setContext($context);
        
        return $this->decision($input)
            ->equal('1', CheckBalanceState::class)
            ->equal('2', MiniStatementState::class)
            ->equal('3', AccountDetailsState::class)
            ->equal('0', MainMenuState::class) // Return to parent
            ->any(AccountServicesState::class);
    }
}

Multi-Level Nesting

You can nest states multiple levels deep. Here's an example with three levels:

Level 1: Main Menu

MainMenuState
  └─> AccountServicesState (Level 2)
        └─> AccountDetailsState (Level 3)

Level 2: Account Services

<?php

class AccountServicesState extends AbstractState
{
    protected function buildMenu(Context $context): Menu
    {
        return (new Menu())
            ->text('Account Services')
            ->line('')
            ->option('1', 'Account Details')
            ->option('2', 'Transaction History')
            ->option('0', 'Back')
            ->expectsInput();
    }

    public function next(Context $context, string $input): ?string
    {
        $this->setContext($context);
        
        return $this->decision($input)
            ->equal('1', AccountDetailsState::class) // Level 3
            ->equal('2', TransactionHistoryState::class)
            ->equal('0', MainMenuState::class) // Back to Level 1
            ->any(AccountServicesState::class);
    }
}

Level 3: Account Details

<?php

class AccountDetailsState extends AbstractState
{
    protected function buildMenu(Context $context): Menu
    {
        $this->setContext($context);
        $accountNumber = $this->record->get('account_number', 'N/A');
        
        return (new Menu())
            ->text('Account Details')
            ->line('')
            ->text("Account: {$accountNumber}")
            ->text("Type: Savings")
            ->text("Status: Active")
            ->line('')
            ->option('0', 'Back to Account Services')
            ->expectsInput();
    }

    public function next(Context $context, string $input): ?string
    {
        $this->setContext($context);
        
        return $this->decision($input)
            ->equal('0', AccountServicesState::class) // Back to Level 2
            ->any(AccountDetailsState::class);
    }
}

Navigation Patterns

1. Back Navigation

Always provide a way for users to go back to the previous level. Common patterns:

// Option 0 for "Back"
->option('0', 'Back to Main Menu')

// In next() method
->equal('0', MainMenuState::class)

// Or use a dedicated BackState
->equal('0', BackState::class)

2. Breadcrumb Navigation

Store navigation history in the Record to enable breadcrumb-style navigation:

<?php

class AccountServicesState extends AbstractState
{
    public function next(Context $context, string $input): ?string
    {
        $this->setContext($context);
        
        // Store parent state before navigating to child
        $this->record->set('previous_state', MainMenuState::class);
        
        return $this->decision($input)
            ->equal('1', AccountDetailsState::class)
            ->equal('0', $this->record->get('previous_state', MainMenuState::class))
            ->any(AccountServicesState::class);
    }
}

3. Direct Home Navigation

Allow users to jump directly to the main menu from any nested state:

<?php

class AccountDetailsState extends AbstractState
{
    protected function buildMenu(Context $context): Menu
    {
        return (new Menu())
            ->text('Account Details')
            ->line('')
            ->text('Account information...')
            ->line('')
            ->option('0', 'Back')
            ->option('*', 'Main Menu') // Quick home navigation
            ->expectsInput();
    }

    public function next(Context $context, string $input): ?string
    {
        $this->setContext($context);
        
        return $this->decision($input)
            ->equal('0', AccountServicesState::class)
            ->equal('*', MainMenuState::class) // Jump to home
            ->any(AccountDetailsState::class);
    }
}

Data Flow in Nested States

Data stored in the Record persists across all nested states. This allows you to:

Storing Data at Parent Level

<?php

class MainMenuState extends AbstractState
{
    public function next(Context $context, string $input): ?string
    {
        $this->setContext($context);
        
        // Store user selection for use in nested states
        if ($input === '1') {
            $this->record->set('selected_service', 'account');
        }
        
        return $this->decision($input)
            ->equal('1', AccountServicesState::class)
            ->any(MainMenuState::class);
    }
}

Accessing Data in Nested States

<?php

class AccountServicesState extends AbstractState
{
    protected function buildMenu(Context $context): Menu
    {
        $this->setContext($context);
        
        // Access data stored in parent state
        $service = $this->record->get('selected_service', 'unknown');
        
        return (new Menu())
            ->text("Account Services ({$service})")
            ->line('')
            ->option('1', 'Check Balance')
            ->option('0', 'Back')
            ->expectsInput();
    }
}

Best Practices

Limit Nesting Depth

Keep nesting to 2-3 levels maximum. Deeper nesting can confuse users and make navigation difficult.

Always Provide Back Option

Every nested state should have a way to return to the parent state. Use option '0' or '*' for back navigation.

Clear Menu Labels

Use descriptive labels that indicate the relationship between parent and child states (e.g., "Back to Main Menu").

Use Record for Context

Store navigation context and user selections in the Record so nested states can access parent state data.

Complete Example: Banking App with Nested States

Here's a complete example showing a banking application with nested states:

MainMenuState
  ├─> AccountServicesState
  │     ├─> CheckBalanceState
  │     ├─> MiniStatementState
  │     └─> AccountDetailsState
  │           └─> AccountSettingsState
  ├─> TransferState
  │     ├─> RecipientState
  │     └─> AmountState
  └─> BillPaymentsState
        ├─> ElectricityState
        ├─> WaterState
        └─> InternetState

This structure allows users to navigate through complex menus while maintaining clear navigation paths and data context throughout the session.