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