Modal State Persistence Patterns

Introduction to Modal State Management

In TSQL.APP, actions often span multiple user interactions through modal interfaces. A common challenge is maintaining consistent state across these interactions, especially when business logic depends on preserving context between modal refreshes, user input, and navigation events.

This section outlines proven patterns to ensure reliable state management in modal-based TSQL.APP applications.

Pattern 1: Complete State Model

Key Concept

Store a complete representation of application state in JSON format during modal interactions rather than relying on individual variables.

Implementation

-- 1. Declare a JSON state variable
DECLARE @JsonState NVARCHAR(MAX);

-- 2. Create comprehensive state using FOR JSON
SET @JsonState = (
    SELECT 
        [@PropertyA]=@VariableA, 
        [@PropertyB]=@VariableB,
        [@PropertyC]=@VariableC 
    FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
);

-- 3. Pass complete state during transitions
EXEC sp_api_modal_restart @values=@JsonState;

Benefits

  • Atomic state updates prevent inconsistencies
  • Complete context preservation between interactions
  • Single source of truth for application state
  • Simplified debugging (examine one JSON value instead of many variables)

When to Use

  • Multi-step processes where context must be preserved
  • Applications with interdependent data elements
  • When state values must remain synchronized (e.g., original/transformed data pairs)
  • Workflow applications with multiple possible paths

Pattern 2: State Synchronization Points

Key Concept

Define explicit points in your code where state is synchronized between variables, modal UI, and JSON persistence.

Implementation

-- 1. Retrieve all state at the beginning
DECLARE @p_name NVARCHAR(MAX);
SET @p_name = N'@state';
EXEC sp_api_modal_get_value @name=@p_name, @value=@JsonState OUT;

-- 2. Parse individual values from JSON if needed
-- (This can be done through direct variable synchronization instead)
SET @p_name = N'@PropertyA';
EXEC sp_api_modal_get_value @name=@p_name, @value=@VariableA OUT;

-- 3. Synchronize state at key transition points
-- After state-changing operations:
SET @JsonState = (
    SELECT /* updated state values */
    FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
);

-- 4. Update state variable without full modal restart when appropriate
SET @p_name = N'@state';
SET @p_value = @JsonState;
EXEC sp_api_modal_value @name=@p_name, @value=@p_value;

Benefits

  • Ensures UI and business logic remain synchronized
  • Prevents "lost update" scenarios
  • Maintains predictable application behavior
  • Isolates state changes to well-defined points

When to Use

  • Time-sensitive operations (e.g., countdown timers)
  • Multi-value form validation
  • Operations with automatic refreshes
  • When modal state might change from multiple entry points

Pattern 3: Atomic State Transitions

Key Concept

Treat state transitions as atomic operations where the complete state is updated in a single operation, followed by immediate termination of the current procedure execution.

Implementation

-- Define transition with complete state update
IF @Condition = 1
BEGIN
    -- 1. Prepare new state
    SET @NewState = N'newState';
    
    -- 2. Update all dependent variables
    SET @VarA = @NewValueA;
    SET @VarB = @NewValueB;
    
    -- 3. Create complete JSON state
    SET @JsonState = (
        SELECT [@State]=@NewState, [@VarA]=@VarA, [@VarB]=@VarB
        FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
    );
    
    -- 4. Execute modal restart with complete state
    EXEC sp_api_modal_restart @values=@JsonState;
    
    -- 5. Terminate current execution to prevent further state changes
    RETURN;
END

Benefits

  • Eliminates partial state updates
  • Prevents race conditions
  • Creates clean, predictable transitions
  • Simplifies debugging and testing

When to Use

  • When transitioning between distinct application states
  • For conditional navigation paths
  • When generating new data that must be synchronized
  • Whenever modal content needs to be completely refreshed

Pattern 4: Hidden State Variables

Key Concept

Use non-displayed variables to maintain complete context that's not directly visible to users but essential for application logic.

Implementation

-- Store complete context in hidden variable
DECLARE @p_name NVARCHAR(MAX);
DECLARE @p_value NVARCHAR(MAX);

-- Original/transformed data pair example
SET @OriginalData = N'SAMPLE';
SET @TransformedData = N'ELPMAS';  -- Reversed for display

-- Store both in state
SET @JsonState = (
    SELECT 
        [@OriginalData]=@OriginalData, 
        [@TransformedData]=@TransformedData,
        [@OtherState]=@OtherState
    FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
);

-- Store complete state in hidden variable
SET @p_name = N'@state';
SET @p_value = @JsonState;
EXEC sp_api_modal_value @name=@p_name, @value=@p_value;

-- Only display transformed data to user
SET @p_name = N'@displayData';
SET @p_value = @TransformedData;
EXEC sp_api_modal_input @name=@p_name, @value=@p_value;

Benefits

  • Maintains complete application context
  • Separates display concerns from data model
  • Enables complex validation against hidden values
  • Improves security by keeping sensitive data out of the UI

When to Use

  • For data transformation/normalization scenarios
  • When maintaining original/processed data pairs
  • In validation scenarios where reference data should be hidden
  • For maintaining comprehensive audit trail

Pattern 5: Race Condition Prevention

Key Concept

Identify and eliminate windows where state could become inconsistent due to timing issues or partial updates.

Common Race Conditions

  1. Display/Validation Mismatch: What user sees doesn't match what system validates against
  2. Lost Updates: Changes made by one process overwritten by another
  3. Partial State: Only some variables updated before an interruption
  4. Stale References: Using outdated references after state has changed

Implementation

-- INCORRECT: Race condition due to separate updates
IF @NewWord IS NULL
BEGIN
    -- Generate new word
    SELECT TOP 1 @NewWord = Word FROM @WordBank ORDER BY NEWID();
    SET @ScrambledWord = REVERSE(@NewWord);
    
    -- Problem: State could be interrupted here, leaving inconsistent state
END

-- Display scrambled word to user
EXEC sp_api_modal_text @text=@ScrambledWord;

-- CORRECT: Atomic update pattern
IF @NewWord IS NULL
BEGIN
    -- Generate new word
    SELECT TOP 1 @NewWord = Word FROM @WordBank ORDER BY NEWID();
    SET @ScrambledWord = REVERSE(@NewWord);
    
    -- Immediately update state and restart
    SET @JsonState = (
        SELECT [@NewWord]=@NewWord, [@ScrambledWord]=@ScrambledWord, [@OtherState]=@OtherState
        FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
    );
    
    EXEC sp_api_modal_restart @values=@JsonState;
    RETURN;  -- Critical: prevents further execution
END

-- Now display scrambled word with confidence it's synchronized
EXEC sp_api_modal_text @text=@ScrambledWord;

Benefits

  • Eliminates timing-dependent bugs
  • Creates predictable user experiences
  • Prevents data corruption or inconsistency
  • Simplifies debugging

When to Use

  • When variables have dependencies on each other
  • In time-sensitive operations
  • For operations requiring multiple modal refresh cycles
  • In any application with paired data (original/transformed)

Pattern 6: State Debugging Techniques

Key Concept

Implement debugging aids during development to visualize and verify state consistency.

Implementation

-- Option 1: Conditional state display
IF @DebugMode = 1
BEGIN
    SET @p_text = CONCAT(N'Debug - State: ', @CurrentState, 
                        N', Original: ', @OriginalData,
                        N', Transformed: ', @TransformedData);
    SET @p_class = N'text-muted small';
    EXEC sp_api_modal_text @text=@p_text, @class=@p_class;
END

-- Option 2: Commented debug statement for development
-- Uncomment during development/testing
/*
SET @p_text = CONCAT(N'Debug - Complete state: ', @JsonState);
SET @p_class = N'text-muted small';
EXEC sp_api_modal_text @text=@p_text, @class=@p_class;
*/

-- Option 3: Hidden state visualization that can be inspected in browser
SET @p_name = N'@debug_state';
SET @p_value = @JsonState;
EXEC sp_api_modal_value @name=@p_name, @value=@p_value;

Benefits

  • Makes state issues visible during development
  • Accelerates troubleshooting
  • Confirms state validity at key points
  • Helps identify synchronization issues

When to Use

  • During initial development
  • When troubleshooting state inconsistencies
  • For complex multi-step processes
  • When implementing new state transitions

Case Study: Word Scramble Game

The following example demonstrates these patterns in a game scenario where maintaining state consistency is critical for proper functionality:

-- Game state transitions with complete state model
IF @GameState = 'round' AND @CurrentWord IS NULL
BEGIN
    -- Select a new word
    SELECT TOP 1 @CurrentWord = Word FROM @WordBank ORDER BY NEWID();
    
    -- Create scrambled version
    SET @ScrambledWord = REVERSE(@CurrentWord);
    
    -- Atomic state transition with complete state
    SET @JsonState = (
        SELECT 
            [@GameState]=@GameState, 
            [@CurrentWord]=@CurrentWord,
            [@ScrambledWord]=@ScrambledWord,
            [@Score]=@Score,
            [@TimeLeft]=30
        FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
    );
    
    -- Restart with complete state before displaying anything
    EXEC sp_api_modal_restart @values=@JsonState;
    RETURN;
END

-- Handle answer submission with validation against stored state
IF @SubmitButton IS NOT NULL
BEGIN
    -- Validate against STORED word (not a newly generated one)
    IF UPPER(@Answer) = UPPER(@CurrentWord)
    BEGIN
        SET @IsCorrect = 1;
        SET @Score = @Score + (@TimeLeft * 10);
    END
    ELSE
    BEGIN
        SET @IsCorrect = 0;
    END
    
    -- Transition to result state atomically
    SET @GameState = 'result';
    SET @JsonState = (
        SELECT 
            [@GameState]=@GameState, 
            [@CurrentWord]=@CurrentWord,
            [@ScrambledWord]=@ScrambledWord,
            [@Answer]=@Answer,
            [@IsCorrect]=@IsCorrect,
            [@Score]=@Score,
            [@TimeLeft]=@TimeLeft
        FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
    );
    
    EXEC sp_api_modal_restart @values=@JsonState;
    RETURN;
END

Best Practices Summary

  1. Always store complete state in JSON format rather than relying on individual variables
  2. Implement atomic state transitions that update all related variables together
  3. Define clear synchronization points where state is synchronized between variables, UI, and persistence
  4. Terminate execution after state transitions using RETURN to prevent inconsistencies
  5. Use hidden state variables for maintaining complete context
  6. Implement debugging visualizations during development to verify state consistency
  7. Identify potential race conditions and eliminate them through atomic transitions

Following these patterns will result in more reliable, predictable TSQL.APP applications with fewer state-related bugs and improved maintainability.