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.