Table of Contents

React Hook Usage

Working with commands in React requires integration with React's component lifecycle and rendering pipeline. The proxy-generated command classes provide a static .use() method that makes commands work seamlessly with React.

Overview

The .use() method integrates commands with React's rendering pipeline and provides automatic re-rendering for state changes such as hasChanges. It internally calls the useCommand() hook and returns a tuple containing:

  1. Command instance - The actual command object with all properties and methods
  2. SetCommandValues function - A function to update multiple command properties at once

Basic Usage

import { OpenDebitAccount } from './generated/commands';

export const MyComponent = () => {
    const [openDebitAccount, setCommandValues] = OpenDebitAccount.use();

    return (
        <></>
    );
};

With Initial Values

The .use() method accepts an optional initialValues parameter to set the command's initial state:

export const MyComponent = () => {
    const [openDebitAccount, setCommandValues] = OpenDebitAccount.use({
        accountId: 'a23edccc-6cb5-44fd-a7a7-7563716fb080',
        name: 'My Account',
        owner: '84cda809-9201-4d8c-8589-0be37c6e3f18'
    });

    return (
        <></>
    );
};

Working with the Command Instance

The first element of the tuple gives you access to the command's properties, methods, and state:

export const MyComponent = () => {
    const [command] = OpenDebitAccount.use();

    const handleSubmit = async () => {
        if (command.hasChanges) {
            const result = await command.execute();
            // Handle result - see CommandResult documentation
        }
    };

    return (
        <input 
            value={command.name} 
            onChange={(e) => command.name = e.target.value}
        />
    );
};

Available Properties and Methods

Properties:

  • hasChanges - Boolean indicating if any property has changed from initial values
  • Command-specific properties (generated from your backend command)

Methods:

  • execute() - Executes the command and returns a CommandResult
  • validate() - Validates the command without executing it (see Validation)
  • setInitialValues(values) - Sets the initial values for change tracking

Updating Multiple Properties

The second element of the tuple allows you to update multiple properties at once, which is useful when loading data from a query or API:

export const MyComponent = () => {
    const [command, setCommandValues] = OpenDebitAccount.use();

    useEffect(() => {
        // Load data from an API or query
        fetchAccountData().then(data => {
            setCommandValues(data);
        });
    }, []);

    return (
        <></>
    );
};

This approach:

  • Updates all specified properties in one operation
  • Triggers a single re-render
  • Maintains change tracking correctly

Complete Example

Here's a complete example showing form handling with a command:

import { OpenDebitAccount } from './generated/commands';
import { useState } from 'react';

export const OpenAccountForm = () => {
    const [command, setCommandValues] = OpenDebitAccount.use({
        accountId: crypto.randomUUID(),
        name: '',
        owner: ''
    });
    const [submitting, setSubmitting] = useState(false);
    const [error, setError] = useState<string | null>(null);

    const handleSubmit = async (e: React.FormEvent) => {
        e.preventDefault();
        
        if (!command.hasChanges) {
            return; // No changes to save
        }

        setSubmitting(true);
        setError(null);

        try {
            const result = await command.execute();
            
            if (result.isSuccess) {
                // Handle success - maybe navigate away or show success message
                console.log('Account opened successfully');
            } else {
                // Handle validation or authorization errors
                if (!result.isValid) {
                    setError('Validation failed: ' + result.validationResults.map(v => v.message).join(', '));
                } else if (!result.isAuthorized) {
                    setError('You are not authorized to perform this action');
                } else {
                    setError('An error occurred');
                }
            }
        } catch (err) {
            setError('Network error: ' + err.message);
        } finally {
            setSubmitting(false);
        }
    };

    return (
        <form onSubmit={handleSubmit}>
            <div>
                <label>
                    Account Name:
                    <input
                        type="text"
                        value={command.name}
                        onChange={(e) => command.name = e.target.value}
                        required
                    />
                </label>
            </div>
            
            <div>
                <label>
                    Owner ID:
                    <input
                        type="text"
                        value={command.owner}
                        onChange={(e) => command.owner = e.target.value}
                        required
                    />
                </label>
            </div>

            {error && <div className="error">{error}</div>}
            
            <button 
                type="submit" 
                disabled={!command.hasChanges || submitting}
            >
                {submitting ? 'Opening Account...' : 'Open Account'}
            </button>
        </form>
    );
};

React Re-rendering

The .use() hook ensures your component re-renders when:

  • Command properties change
  • hasChanges state changes
  • Command execution completes

This provides a seamless integration between the command pattern and React's declarative UI model.

Best Practices

  1. Use Destructuring: Destructure only what you need from the tuple

    const [command] = OpenDebitAccount.use(); // If you don't need setCommandValues
    
  2. Initialize with Data: Pass initial values when you have them

    const [command] = OpenDebitAccount.use(existingData);
    
  3. Check hasChanges: Prevent unnecessary submissions

    if (!command.hasChanges) return;
    
  4. Handle All Result States: Always check isSuccess, isValid, and isAuthorized

  5. Use CommandForm: For complex forms, consider using CommandForm which handles much of this automatically

See Also