Creating Custom Validators
Creating Custom Validators
Section titled “Creating Custom Validators”While Phaser Hooks provides built-in validators, you can create your own custom validators to meet specific validation needs.
Validator Function Signature
Section titled “Validator Function Signature”A validator function must follow this signature:
type StateValidator = (value: unknown) => boolean | string;- Input:
value- The value to validate - Output:
trueif valid,falseorstringerror message if invalid
Basic Custom Validator
Section titled “Basic Custom Validator”import { withLocalState } from 'phaser-hooks';
// Custom validator for email formatfunction emailValidator(value: unknown): boolean | string { const email = value as string;
if (typeof email !== 'string') { return 'Value must be a string'; }
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { return 'Value must be a valid email address'; }
return true;}
// Custom validator for positive numbersfunction positiveNumberValidator(value: unknown): boolean | string { const num = value as number;
if (typeof num !== 'number' || Number.isNaN(num)) { return 'Value must be a number'; }
if (num <= 0) { return 'Value must be a positive number'; }
return true;}
export class GameScene extends Phaser.Scene { create() { // Use custom validators const playerEmail = withLocalState(this, 'email', 'player@game.com', { validator: emailValidator });
const playerScore = withLocalState(this, 'score', 100, { validator: positiveNumberValidator }); }}import { withLocalState } from 'phaser-hooks';
// Custom validator for email formatfunction emailValidator(value) { const email = value;
if (typeof email !== 'string') { return 'Value must be a string'; }
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { return 'Value must be a valid email address'; }
return true;}
// Custom validator for positive numbersfunction positiveNumberValidator(value) { const num = value;
if (typeof num !== 'number' || Number.isNaN(num)) { return 'Value must be a number'; }
if (num <= 0) { return 'Value must be a positive number'; }
return true;}
export class GameScene extends Phaser.Scene { create() { // Use custom validators const playerEmail = withLocalState(this, 'email', 'player@game.com', { validator: emailValidator });
const playerScore = withLocalState(this, 'score', 100, { validator: positiveNumberValidator }); }}Advanced Custom Validators
Section titled “Advanced Custom Validators”Validator Factory Functions
Section titled “Validator Factory Functions”// Validator factory for string lengthfunction stringLengthValidator(minLength: number, maxLength?: number) { return (value: unknown): boolean | string => { const str = value as string;
if (typeof str !== 'string') { return 'Value must be a string'; }
if (str.length < minLength) { return `String must be at least ${minLength} characters long`; }
if (maxLength !== undefined && str.length > maxLength) { return `String must be at most ${maxLength} characters long`; }
return true; };}
// Validator factory for object propertiesfunction objectPropertyValidator(requiredProps: string[]) { return (value: unknown): boolean | string => { if (typeof value !== 'object' || value === null) { return 'Value must be an object'; }
const obj = value as Record<string, any>;
for (const prop of requiredProps) { if (!(prop in obj)) { return `Object must have property: ${prop}`; } }
return true; };}
export class GameScene extends Phaser.Scene { create() { // Use validator factories const playerName = withLocalState(this, 'playerName', 'Player', { validator: stringLengthValidator(3, 20) });
const playerData = withLocalState(this, 'playerData', { name: 'Player', level: 1 }, { validator: objectPropertyValidator(['name', 'level']) }); }}// Validator factory for string lengthfunction stringLengthValidator(minLength, maxLength) { return (value) => { const str = value;
if (typeof str !== 'string') { return 'Value must be a string'; }
if (str.length < minLength) { return `String must be at least ${minLength} characters long`; }
if (maxLength !== undefined && str.length > maxLength) { return `String must be at most ${maxLength} characters long`; }
return true; };}
// Validator factory for object propertiesfunction objectPropertyValidator(requiredProps) { return (value) => { if (typeof value !== 'object' || value === null) { return 'Value must be an object'; }
const obj = value;
for (const prop of requiredProps) { if (!(prop in obj)) { return `Object must have property: ${prop}`; } }
return true; };}
export class GameScene extends Phaser.Scene { create() { // Use validator factories const playerName = withLocalState(this, 'playerName', 'Player', { validator: stringLengthValidator(3, 20) });
const playerData = withLocalState(this, 'playerData', { name: 'Player', level: 1 }, { validator: objectPropertyValidator(['name', 'level']) }); }}Complex Validation Logic
Section titled “Complex Validation Logic”// Complex validator for game coordinatesfunction coordinateValidator(maxX: number, maxY: number) { return (value: unknown): boolean | string => { if (typeof value !== 'object' || value === null) { return 'Value must be an object'; }
const coord = value as { x: number; y: number };
if (typeof coord.x !== 'number' || typeof coord.y !== 'number') { return 'Coordinates must have numeric x and y properties'; }
if (coord.x < 0 || coord.x > maxX) { return `X coordinate must be between 0 and ${maxX}`; }
if (coord.y < 0 || coord.y > maxY) { return `Y coordinate must be between 0 and ${maxY}`; }
return true; };}
// Validator for inventory itemsfunction inventoryItemValidator() { return (value: unknown): boolean | string => { if (typeof value !== 'object' || value === null) { return 'Value must be an object'; }
const item = value as { id: string; name: string; quantity: number };
if (typeof item.id !== 'string' || item.id.trim() === '') { return 'Item must have a valid ID'; }
if (typeof item.name !== 'string' || item.name.trim() === '') { return 'Item must have a valid name'; }
if (typeof item.quantity !== 'number' || item.quantity < 0) { return 'Item quantity must be a non-negative number'; }
return true; };}
export class GameScene extends Phaser.Scene { create() { const playerPosition = withLocalState(this, 'position', { x: 0, y: 0 }, { validator: coordinateValidator(800, 600) });
const inventoryItem = withLocalState(this, 'item', { id: 'sword', name: 'Iron Sword', quantity: 1 }, { validator: inventoryItemValidator() }); }}// Complex validator for game coordinatesfunction coordinateValidator(maxX, maxY) { return (value) => { if (typeof value !== 'object' || value === null) { return 'Value must be an object'; }
const coord = value;
if (typeof coord.x !== 'number' || typeof coord.y !== 'number') { return 'Coordinates must have numeric x and y properties'; }
if (coord.x < 0 || coord.x > maxX) { return `X coordinate must be between 0 and ${maxX}`; }
if (coord.y < 0 || coord.y > maxY) { return `Y coordinate must be between 0 and ${maxY}`; }
return true; };}
// Validator for inventory itemsfunction inventoryItemValidator() { return (value) => { if (typeof value !== 'object' || value === null) { return 'Value must be an object'; }
const item = value;
if (typeof item.id !== 'string' || item.id.trim() === '') { return 'Item must have a valid ID'; }
if (typeof item.name !== 'string' || item.name.trim() === '') { return 'Item must have a valid name'; }
if (typeof item.quantity !== 'number' || item.quantity < 0) { return 'Item quantity must be a non-negative number'; }
return true; };}
export class GameScene extends Phaser.Scene { create() { const playerPosition = withLocalState(this, 'position', { x: 0, y: 0 }, { validator: coordinateValidator(800, 600) });
const inventoryItem = withLocalState(this, 'item', { id: 'sword', name: 'Iron Sword', quantity: 1 }, { validator: inventoryItemValidator() }); }}Error Handling with Custom Validators
Section titled “Error Handling with Custom Validators”function customEmailValidator(value: unknown): boolean | string { const email = value as string;
if (typeof email !== 'string') { return 'Value must be a string'; }
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { return 'Value must be a valid email address'; }
return true;}
export class GameScene extends Phaser.Scene { create() { const playerEmail = withLocalState(this, 'email', 'player@game.com', { validator: customEmailValidator });
function updateEmail(newEmail: string) { try { playerEmail.set(newEmail); console.log('Email updated successfully'); return true; } catch (error) { console.error('Email validation failed:', error.message); // Handle the error appropriately this.showErrorMessage('Please enter a valid email address'); return false; } } }}function customEmailValidator(value) { const email = value;
if (typeof email !== 'string') { return 'Value must be a string'; }
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { return 'Value must be a valid email address'; }
return true;}
export class GameScene extends Phaser.Scene { create() { const playerEmail = withLocalState(this, 'email', 'player@game.com', { validator: customEmailValidator });
function updateEmail(newEmail) { try { playerEmail.set(newEmail); console.log('Email updated successfully'); return true; } catch (error) { console.error('Email validation failed:', error.message); // Handle the error appropriately this.showErrorMessage('Please enter a valid email address'); return false; } } }}Best Practices
Section titled “Best Practices”1. Clear Error Messages
Section titled “1. Clear Error Messages”// ✅ Good - Clear error messagefunction goodValidator(value: unknown): boolean | string { if (typeof value !== 'string') { return 'Value must be a string'; } return true;}
// ❌ Bad - Unclear error messagefunction badValidator(value: unknown): boolean | string { if (typeof value !== 'string') { return 'Invalid'; } return true;}2. Type Safety
Section titled “2. Type Safety”// ✅ Good - Type-safe validationfunction typeSafeValidator(value: unknown): boolean | string { const obj = value as { id: string; name: string };
if (typeof obj.id !== 'string') { return 'ID must be a string'; }
if (typeof obj.name !== 'string') { return 'Name must be a string'; }
return true;}3. Reusable Validators
Section titled “3. Reusable Validators”// ✅ Good - Reusable validator factoryfunction createRangeValidator(min: number, max: number) { return (value: unknown): boolean | string => { const num = value as number; if (typeof num !== 'number') { return 'Value must be a number'; } if (num < min || num > max) { return `Value must be between ${min} and ${max}`; } return true; };}
// Usageconst healthValidator = createRangeValidator(0, 100);const scoreValidator = createRangeValidator(0, 999999);Common Custom Validator Patterns
Section titled “Common Custom Validator Patterns”URL Validator
Section titled “URL Validator”function urlValidator(value: unknown): boolean | string { const url = value as string;
if (typeof url !== 'string') { return 'Value must be a string'; }
try { new URL(url); return true; } catch { return 'Value must be a valid URL'; }}Date Validator
Section titled “Date Validator”function dateValidator(value: unknown): boolean | string { const date = value as string;
if (typeof date !== 'string') { return 'Value must be a string'; }
const parsedDate = new Date(date); if (isNaN(parsedDate.getTime())) { return 'Value must be a valid date'; }
return true;}Password Strength Validator
Section titled “Password Strength Validator”function passwordValidator(minLength: number = 8) { return (value: unknown): boolean | string => { const password = value as string;
if (typeof password !== 'string') { return 'Value must be a string'; }
if (password.length < minLength) { return `Password must be at least ${minLength} characters long`; }
if (!/[A-Z]/.test(password)) { return 'Password must contain at least one uppercase letter'; }
if (!/[a-z]/.test(password)) { return 'Password must contain at least one lowercase letter'; }
if (!/\d/.test(password)) { return 'Password must contain at least one number'; }
return true; };}Next Steps
Section titled “Next Steps”- Validators Overview - Return to validators overview
- Number Range Validator - Learn about number validation
- Non-Empty String Validator - Learn about string validation