Validators Reference
Overview
Section titled “Overview”Validators ensure state values meet specific criteria before being set. Phaser Hooks provides built-in validators for common patterns and supports custom validation functions.
Validator Function Signature
Section titled “Validator Function Signature”type Validator<T> = (value: T) => true | string;- Return
trueif the value is valid - Return a string with an error message if invalid
Built-in Validators
Section titled “Built-in Validators”validators.numberRange(min, max)
Section titled “validators.numberRange(min, max)”Validates that a number falls within a specified range.
validators.numberRange(min: number, max: number): Validator<number>Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
min | number | Minimum allowed value (inclusive) |
max | number | Maximum allowed value (inclusive) |
Returns
Section titled “Returns”Validator<number> - Validator function
Example
Section titled “Example”import { withLocalState, validators } from 'phaser-hooks';
const scoreState = withLocalState(this, 'score', 0, { validator: validators.numberRange(0, 1000),});
scoreState.set(500); // ValidscoreState.set(1000); // Valid
try { scoreState.set(1500); // Throws error} catch (error) { console.error(error.message); // "Value must be between 0 and 1000"}
try { scoreState.set(-10); // Throws error} catch (error) { console.error(error.message); // "Value must be between 0 and 1000"}Use Cases
Section titled “Use Cases”- Score limits
- Health/mana values
- Volume controls (0-1)
- Level ranges
validators.nonEmptyString
Section titled “validators.nonEmptyString”Validates that a string is not empty (after trimming whitespace).
validators.nonEmptyString: Validator<string>Example
Section titled “Example”import { withLocalState, validators } from 'phaser-hooks';
const nameState = withLocalState(this, 'name', '', { validator: validators.nonEmptyString,});
nameState.set('Player1'); // ValidnameState.set(' John '); // Valid
try { nameState.set(''); // Throws error} catch (error) { console.error(error.message); // "Value cannot be an empty string"}
try { nameState.set(' '); // Throws error (whitespace only)} catch (error) { console.error(error.message); // "Value cannot be an empty string"}Use Cases
Section titled “Use Cases”- Player names
- Input fields
- Required text fields
- Search queries
validators.arrayLength(min, max?)
Section titled “validators.arrayLength(min, max?)”Validates that an array’s length falls within a specified range.
validators.arrayLength(min: number, max?: number): Validator<any[]>Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
min | number | Minimum array length (inclusive) |
max | number (optional) | Maximum array length (inclusive). Omit for no upper limit. |
Returns
Section titled “Returns”Validator<any[]> - Validator function
Example
Section titled “Example”import { withLocalState, validators } from 'phaser-hooks';
// Inventory with 2-4 itemsconst inventoryState = withLocalState<string[]>(this, 'inventory', [], { validator: validators.arrayLength(2, 4),});
inventoryState.set(['sword', 'potion']); // Valid (2 items)inventoryState.set(['sword', 'potion', 'shield']); // Valid (3 items)inventoryState.set(['sword', 'potion', 'shield', 'armor']); // Valid (4 items)
try { inventoryState.set(['sword']); // Throws error (only 1 item)} catch (error) { console.error(error.message); // "Array length must be between 2 and 4"}
try { // Throws error (5 items) inventoryState.set(['sword', 'potion', 'shield', 'armor', 'ring']);} catch (error) { console.error(error.message); // "Array length must be between 2 and 4"}
// No maximum limitconst listState = withLocalState<number[]>(this, 'list', [], { validator: validators.arrayLength(1), // At least 1 item, no max});
listState.set([1]); // ValidlistState.set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); // Valid (no max limit)Use Cases
Section titled “Use Cases”- Inventory systems
- Party member limits
- Skill slot restrictions
- Queue management
validators.oneOf(allowedValues)
Section titled “validators.oneOf(allowedValues)”Validates that a value is one of the allowed values.
validators.oneOf<T>(allowedValues: T[]): Validator<T>Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
allowedValues | T[] | Array of allowed values |
Returns
Section titled “Returns”Validator<T> - Validator function
Example
Section titled “Example”import { withGlobalState, validators } from 'phaser-hooks';
type Difficulty = 'easy' | 'normal' | 'hard';
const difficultyState = withGlobalState<Difficulty>( this, 'difficulty', 'normal', { validator: validators.oneOf(['easy', 'normal', 'hard']), });
difficultyState.set('easy'); // ValiddifficultyState.set('hard'); // Valid
try { difficultyState.set('extreme' as Difficulty); // Throws error} catch (error) { console.error(error.message); // "Value must be one of: easy, normal, hard"}
// With other typesconst qualityState = withLocalState<number>(this, 'quality', 1, { validator: validators.oneOf([1, 2, 3, 4, 5]),});
qualityState.set(3); // Valid
try { qualityState.set(10); // Throws error} catch (error) { console.error(error.message); // "Value must be one of: 1, 2, 3, 4, 5"}Use Cases
Section titled “Use Cases”- Difficulty levels
- Quality settings
- Game modes
- Character classes
- Enum-like values
Creating Custom Validators
Section titled “Creating Custom Validators”Basic Custom Validator
Section titled “Basic Custom Validator”const healthValidator = (value: number): true | string => { if (value < 0) { return 'Health cannot be negative'; }
if (value > 100) { return 'Health cannot exceed 100'; }
return true;};
const healthState = withLocalState(this, 'health', 100, { validator: healthValidator,});Complex Object Validator
Section titled “Complex Object Validator”interface PlayerData { name: string; level: number; inventory: string[];}
const playerValidator = (value: PlayerData): true | string => { // Validate name if (!value.name || value.name.trim().length < 3) { return 'Player name must be at least 3 characters'; }
// Validate level if (value.level < 1 || value.level > 100) { return 'Player level must be between 1 and 100'; }
// Validate inventory if (value.inventory.length > 20) { return 'Inventory cannot exceed 20 items'; }
// Check for duplicate items const uniqueItems = new Set(value.inventory); if (uniqueItems.size !== value.inventory.length) { return 'Inventory cannot contain duplicate items'; }
return true;};
const playerState = withLocalState<PlayerData>( this, 'player', { name: 'Player', level: 1, inventory: [], }, { validator: playerValidator });Email Validator
Section titled “Email Validator”const emailValidator = (value: string): true | string => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) { return 'Invalid email address format'; }
return true;};
const emailState = withLocalState(this, 'email', '', { validator: emailValidator,});Username Validator
Section titled “Username Validator”const usernameValidator = (value: string): true | string => { // Check length if (value.length < 3 || value.length > 20) { return 'Username must be between 3 and 20 characters'; }
// Check characters if (!/^[a-zA-Z0-9_]+$/.test(value)) { return 'Username can only contain letters, numbers, and underscores'; }
// Cannot start with number if (/^[0-9]/.test(value)) { return 'Username cannot start with a number'; }
return true;};
const usernameState = withLocalState(this, 'username', '', { validator: usernameValidator,});Range Validator with Custom Message
Section titled “Range Validator with Custom Message”const createRangeValidator = (min: number, max: number, fieldName: string) => { return (value: number): true | string => { if (value < min || value > max) { return `${fieldName} must be between ${min} and ${max}`; } return true; };};
const ageState = withLocalState(this, 'age', 18, { validator: createRangeValidator(13, 120, 'Age'),});Composing Validators
Section titled “Composing Validators”Combine multiple validators:
const composeValidators = <T>(...validators: Validator<T>[]): Validator<T> => { return (value: T): true | string => { for (const validator of validators) { const result = validator(value); if (result !== true) { return result; // Return first error } } return true; };};
// Usageconst usernameState = withLocalState(this, 'username', '', { validator: composeValidators( validators.nonEmptyString, (value: string) => { if (value.length < 3) return 'Username too short'; return true; }, (value: string) => { if (!/^[a-zA-Z]/.test(value)) return 'Username must start with a letter'; return true; } ),});Conditional Validation
Section titled “Conditional Validation”Apply different validation based on context:
export class GameScene extends Phaser.Scene { private isDeveloperMode = false;
create() { const scoreValidator = (value: number): true | string => { // Skip validation in developer mode if (this.isDeveloperMode) { return true; }
// Normal validation if (value < 0 || value > 999999) { return 'Score must be between 0 and 999999'; }
return true; };
const scoreState = withLocalState(this, 'score', 0, { validator: scoreValidator, }); }}Best Practices
Section titled “Best Practices”1. Keep Validators Pure
Section titled “1. Keep Validators Pure”Validators should not have side effects:
// ❌ Bad - has side effectsconst badValidator = (value: number): true | string => { console.log('Validating...'); // Side effect this.someProperty = value; // Side effect return value >= 0 ? true : 'Invalid';};
// ✅ Good - pure functionconst goodValidator = (value: number): true | string => { return value >= 0 ? true : 'Invalid';};2. Provide Clear Error Messages
Section titled “2. Provide Clear Error Messages”// ❌ Bad - vague errorconst badValidator = (value: string): true | string => { return value.length >= 3 ? true : 'Invalid';};
// ✅ Good - descriptive errorconst goodValidator = (value: string): true | string => { return value.length >= 3 ? true : 'Username must be at least 3 characters';};3. Validate Early
Section titled “3. Validate Early”Catch errors before they cause problems:
const playerState = withLocalState( this, 'player', { hp: 100 }, { validator: (player) => { if (player.hp < 0) return 'HP cannot be negative'; return true; }, });
try { playerState.set({ hp: -10 });} catch (error) { // Handle error immediately console.error('Invalid player state:', error.message); this.showErrorToUser(error.message);}4. Test Your Validators
Section titled “4. Test Your Validators”// Test validator with various inputsconst validator = validators.numberRange(0, 100);
console.assert(validator(50) === true);console.assert(validator(0) === true);console.assert(validator(100) === true);console.assert(typeof validator(-1) === 'string');console.assert(typeof validator(101) === 'string');Next Steps
Section titled “Next Steps”- Validation Guide - Learn about using validators
- API Reference - Complete API documentation
- Hook State Interface - HookState reference