Schema State Hooks

The View Builder package provides React hooks for managing view schema state. These hooks enable components to access and modify the schema, validate it, and persist it to storage.

Features

  • Centralized State Management: Single source of truth for schema data
  • Schema Validation: Validate schemas against specifications
  • Persistence: Save and load schemas from storage
  • Field Management: Add, update, and remove fields
  • Type Safety: TypeScript definitions for all schema types

useSchemaState Hook

The useSchemaState hook provides access to the schema state and operations:

import { useSchemaState } from '@repo/view-builder/hooks/useSchemaState';

function SchemaManager() {
  const { 
    schema,
    setSchema,
    addField,
    updateField,
    removeField,
    validateSchema,
    persistView,
    loadView,
    deleteView,
    getViewList
  } = useSchemaState();
  
  // Use schema state and operations
  return (
    <div>
      <h3>Current Schema</h3>
      <pre>{JSON.stringify(schema, null, 2)}</pre>
    </div>
  );
}

SchemaProvider Component

The SchemaProvider component provides the schema state context to its children:

import { SchemaProvider } from '@repo/view-builder/hooks/useSchemaState';

function ViewBuilderApp() {
  return (
    <SchemaProvider>
      {/* Components that need access to schema state */}
    </SchemaProvider>
  );
}

Schema Operations

Adding a Field

const { addField } = useSchemaState();

const handleAddField = () => {
  addField({
    name: 'email',
    label: 'Email Address',
    type: 'string',
    required: true,
    validation: {
      pattern: '^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$',
      message: 'Please enter a valid email address'
    }
  });
};

Updating a Field

const { updateField } = useSchemaState();

const handleUpdateField = () => {
  updateField('email', {
    label: 'Contact Email',
    required: false
  });
};

Removing a Field

const { removeField } = useSchemaState();

const handleRemoveField = () => {
  removeField('email');
};

Validating the Schema

const { validateSchema } = useSchemaState();

const handleValidate = () => {
  const { valid, errors } = validateSchema();
  if (!valid) {
    console.error('Schema validation failed:', errors);
  } else {
    console.log('Schema is valid');
  }
};

Persisting the Schema

const { persistView } = useSchemaState();

const handleSave = async () => {
  try {
    const id = await persistView('my-view');
    console.log(`Saved view with ID: ${id}`);
  } catch (error) {
    console.error('Failed to save view:', error);
  }
};

Loading a Schema

const { loadView } = useSchemaState();

const handleLoad = async () => {
  try {
    const success = await loadView('my-view');
    if (success) {
      console.log('Loaded view successfully');
    } else {
      console.error('Failed to load view');
    }
  } catch (error) {
    console.error('Error loading view:', error);
  }
};

Advanced Usage

Custom Initial Schema

You can provide a custom initial schema to the provider:

import { SchemaProvider } from '@repo/view-builder/hooks/useSchemaState';

const customInitialSchema = {
  type: 'form',
  schema: 'product',
  fields: {
    name: {
      label: 'Product Name',
      type: 'string',
      required: true
    },
    price: {
      label: 'Price',
      type: 'number',
      required: true
    }
  }
};

function CustomSchemaApp() {
  return (
    <SchemaProvider initialSchema={customInitialSchema}>
      {/* Components that need access to schema state */}
    </SchemaProvider>
  );
}

Custom Storage Provider

You can provide a custom storage provider:

import { SchemaProvider } from '@repo/view-builder/hooks/useSchemaState';

const customStorageProvider = {
  saveView: async (id, schema) => {
    // Custom implementation for saving views
    await fetch(`/api/views/${id}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(schema)
    });
    return id;
  },
  getView: async (id) => {
    // Custom implementation for getting views
    const response = await fetch(`/api/views/${id}`);
    return response.json();
  },
  listViews: async () => {
    // Custom implementation for listing views
    const response = await fetch('/api/views');
    const views = await response.json();
    return views.map(view => view.id);
  },
  deleteView: async (id) => {
    // Custom implementation for deleting views
    await fetch(`/api/views/${id}`, { method: 'DELETE' });
  }
};

function CustomStorageApp() {
  return (
    <SchemaProvider storageProvider={customStorageProvider}>
      {/* Components that need access to schema state */}
    </SchemaProvider>
  );
}

Schema History

You can implement undo/redo functionality:

import { useSchemaState } from '@repo/view-builder/hooks/useSchemaState';
import { useState } from 'react';

function SchemaHistoryManager() {
  const { schema, setSchema } = useSchemaState();
  const [history, setHistory] = useState([schema]);
  const [historyIndex, setHistoryIndex] = useState(0);
  
  const handleSchemaChange = (newSchema) => {
    // Add new schema to history
    const newHistory = history.slice(0, historyIndex + 1);
    newHistory.push(newSchema);
    setHistory(newHistory);
    setHistoryIndex(newHistory.length - 1);
    setSchema(newSchema);
  };
  
  const handleUndo = () => {
    if (historyIndex > 0) {
      setHistoryIndex(historyIndex - 1);
      setSchema(history[historyIndex - 1]);
    }
  };
  
  const handleRedo = () => {
    if (historyIndex < history.length - 1) {
      setHistoryIndex(historyIndex + 1);
      setSchema(history[historyIndex + 1]);
    }
  };
  
  return (
    <div>
      <button onClick={handleUndo} disabled={historyIndex === 0}>Undo</button>
      <button onClick={handleRedo} disabled={historyIndex === history.length - 1}>Redo</button>
    </div>
  );
}