Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.zopio.dev/llms.txt

Use this file to discover all available pages before exploring further.

Data UI Components

The UI package provides React components and hooks for working with data in the zopio framework. It offers a seamless integration between your data providers and React components.

Features

  • React Hooks: Hooks for data fetching and manipulation
  • UI Components: Ready-to-use components for displaying data
  • Form Integration: Tools for building data-driven forms
  • Optimistic Updates: Support for optimistic UI updates
  • Loading States: Handling loading and error states

React Hooks

Data Provider Hook

The useDataProvider hook gives you access to the data provider in your components:
import { ui } from '@repo/data';

function MyComponent() {
  const dataProvider = ui.useDataProvider();
  
  const handleClick = async () => {
    const { data } = await dataProvider.getList({
      resource: 'users',
      pagination: { page: 1, perPage: 10 }
    });
    console.log(data);
  };
  
  return (
    <button onClick={handleClick}>Load Users</button>
  );
}

CRUD Operation Hooks

The UI package provides hooks for each CRUD operation:

useGetList

import { ui } from '@repo/data';

function UserList() {
  const { data, loading, error, total, refetch } = ui.useGetList('users', {
    pagination: { page: 1, perPage: 10 },
    sort: { field: 'name', order: 'asc' },
    filter: { role: 'admin' }
  });
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <div>
      <button onClick={refetch}>Refresh</button>
      <p>Total: {total}</p>
      <ul>
        {data.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

useGetOne

import { ui } from '@repo/data';

function UserDetails({ id }) {
  const { data, loading, error, refetch } = ui.useGetOne('users', id);
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <div>
      <button onClick={refetch}>Refresh</button>
      <h2>{data.name}</h2>
      <p>Email: {data.email}</p>
      <p>Role: {data.role}</p>
    </div>
  );
}

useCreate

import { ui } from '@repo/data';

function CreateUser() {
  const [create, { loading, error }] = ui.useCreate('users');
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    const data = {
      name: formData.get('name'),
      email: formData.get('email'),
      role: formData.get('role')
    };
    
    try {
      const result = await create(data);
      console.log('Created user:', result.data);
    } catch (err) {
      console.error('Failed to create user:', err);
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="name">Name</label>
        <input type="text" id="name" name="name" required />
      </div>
      <div>
        <label htmlFor="email">Email</label>
        <input type="email" id="email" name="email" required />
      </div>
      <div>
        <label htmlFor="role">Role</label>
        <select id="role" name="role">
          <option value="user">User</option>
          <option value="admin">Admin</option>
        </select>
      </div>
      <button type="submit" disabled={loading}>
        {loading ? 'Creating...' : 'Create User'}
      </button>
      {error && <div>Error: {error.message}</div>}
    </form>
  );
}

useUpdate

import { ui } from '@repo/data';

function UpdateUser({ id }) {
  const { data, loading: fetchLoading } = ui.useGetOne('users', id);
  const [update, { loading: updateLoading, error }] = ui.useUpdate('users');
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    const data = {
      name: formData.get('name'),
      email: formData.get('email'),
      role: formData.get('role')
    };
    
    try {
      const result = await update(id, data);
      console.log('Updated user:', result.data);
    } catch (err) {
      console.error('Failed to update user:', err);
    }
  };
  
  if (fetchLoading) return <div>Loading...</div>;
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="name">Name</label>
        <input type="text" id="name" name="name" defaultValue={data.name} required />
      </div>
      <div>
        <label htmlFor="email">Email</label>
        <input type="email" id="email" name="email" defaultValue={data.email} required />
      </div>
      <div>
        <label htmlFor="role">Role</label>
        <select id="role" name="role" defaultValue={data.role}>
          <option value="user">User</option>
          <option value="admin">Admin</option>
        </select>
      </div>
      <button type="submit" disabled={updateLoading}>
        {updateLoading ? 'Updating...' : 'Update User'}
      </button>
      {error && <div>Error: {error.message}</div>}
    </form>
  );
}

useDelete

import { ui } from '@repo/data';

function DeleteUser({ id, onDeleted }) {
  const [deleteUser, { loading, error }] = ui.useDelete('users');
  
  const handleDelete = async () => {
    if (window.confirm('Are you sure you want to delete this user?')) {
      try {
        await deleteUser(id);
        onDeleted();
      } catch (err) {
        console.error('Failed to delete user:', err);
      }
    }
  };
  
  return (
    <div>
      <button onClick={handleDelete} disabled={loading}>
        {loading ? 'Deleting...' : 'Delete User'}
      </button>
      {error && <div>Error: {error.message}</div>}
    </div>
  );
}

Mutation Hooks

The UI package provides hooks for mutations with optimistic updates:
import { ui } from '@repo/data';

function ToggleUserStatus({ id, status }) {
  const { mutate, loading, error } = ui.useMutation({
    mutationFn: async () => {
      const newStatus = status === 'active' ? 'inactive' : 'active';
      const { data } = await dataProvider.update({
        resource: 'users',
        id,
        data: { status: newStatus }
      });
      return data;
    },
    // Optimistic update
    optimisticUpdate: {
      resource: 'users',
      id,
      data: { status: status === 'active' ? 'inactive' : 'active' }
    }
  });
  
  return (
    <button onClick={mutate} disabled={loading}>
      {loading ? 'Updating...' : `Mark as ${status === 'active' ? 'Inactive' : 'Active'}`}
    </button>
  );
}

UI Components

DataProvider Component

The DataProvider component provides the data context to your application:
import { ui } from '@repo/data';
import { providers } from '@repo/data';

const dataProvider = providers.supabase.createProvider({
  url: process.env.SUPABASE_URL,
  key: process.env.SUPABASE_KEY
});

function App() {
  return (
    <ui.DataProvider dataProvider={dataProvider}>
      <UserList />
    </ui.DataProvider>
  );
}

DataList Component

The DataList component displays a list of resources:
import { ui } from '@repo/data';

function UserList() {
  return (
    <ui.DataList
      resource="users"
      columns={[
        { field: 'id', headerName: 'ID' },
        { field: 'name', headerName: 'Name' },
        { field: 'email', headerName: 'Email' },
        { field: 'role', headerName: 'Role' }
      ]}
      pagination={{ page: 1, perPage: 10 }}
      sort={{ field: 'name', order: 'asc' }}
      filter={{ role: 'admin' }}
      onRowClick={(row) => console.log('Clicked row:', row)}
    />
  );
}

DataDetails Component

The DataDetails component displays the details of a resource:
import { ui } from '@repo/data';

function UserDetails({ id }) {
  return (
    <ui.DataDetails
      resource="users"
      id={id}
      fields={[
        { field: 'id', label: 'ID' },
        { field: 'name', label: 'Name' },
        { field: 'email', label: 'Email' },
        { field: 'role', label: 'Role' },
        { field: 'createdAt', label: 'Created At', render: (value) => new Date(value).toLocaleString() }
      ]}
    />
  );
}

DataForm Component

The DataForm component provides a form for creating or updating a resource:
import { ui } from '@repo/data';

function UserForm({ id }) {
  return (
    <ui.DataForm
      resource="users"
      id={id} // Omit for create form
      fields={[
        { field: 'name', label: 'Name', type: 'text', required: true },
        { field: 'email', label: 'Email', type: 'email', required: true },
        { field: 'role', label: 'Role', type: 'select', options: [
          { value: 'user', label: 'User' },
          { value: 'admin', label: 'Admin' }
        ]}
      ]}
      onSuccess={(data) => console.log('Form submitted:', data)}
    />
  );
}

DataPagination Component

The DataPagination component provides pagination controls:
import { ui } from '@repo/data';

function UserListPagination() {
  const [page, setPage] = useState(1);
  const { data, total } = ui.useGetList('users', {
    pagination: { page, perPage: 10 }
  });
  
  return (
    <div>
      <ul>
        {data.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
      <ui.DataPagination
        page={page}
        perPage={10}
        total={total}
        onChange={setPage}
      />
    </div>
  );
}

DataFilter Component

The DataFilter component provides filtering controls:
import { ui } from '@repo/data';

function UserListFilter() {
  const [filter, setFilter] = useState({});
  const { data } = ui.useGetList('users', { filter });
  
  return (
    <div>
      <ui.DataFilter
        fields={[
          { field: 'name', label: 'Name', type: 'text' },
          { field: 'role', label: 'Role', type: 'select', options: [
            { value: 'user', label: 'User' },
            { value: 'admin', label: 'Admin' }
          ]}
        ]}
        filter={filter}
        onChange={setFilter}
      />
      <ul>
        {data.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

Form Integration

useDataForm Hook

The useDataForm hook provides form functionality for data operations:
import { ui } from '@repo/data';

function UserForm({ id }) {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
    reset,
    defaultValues
  } = ui.useDataForm({
    resource: 'users',
    id, // Omit for create form
    onSuccess: (data) => {
      console.log('Form submitted:', data);
      reset(data);
    }
  });
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="name">Name</label>
        <input id="name" {...register('name', { required: 'Name is required' })} />
        {errors.name && <span>{errors.name.message}</span>}
      </div>
      <div>
        <label htmlFor="email">Email</label>
        <input id="email" type="email" {...register('email', { 
          required: 'Email is required',
          pattern: {
            value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
            message: 'Invalid email address'
          }
        })} />
        {errors.email && <span>{errors.email.message}</span>}
      </div>
      <div>
        <label htmlFor="role">Role</label>
        <select id="role" {...register('role')}>
          <option value="user">User</option>
          <option value="admin">Admin</option>
        </select>
      </div>
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Submitting...' : id ? 'Update User' : 'Create User'}
      </button>
    </form>
  );
}

Field Components

The UI package provides field components for building forms:
import { ui } from '@repo/data';

function UserForm({ id }) {
  const { control, handleSubmit, isSubmitting } = ui.useDataForm({
    resource: 'users',
    id
  });
  
  return (
    <form onSubmit={handleSubmit}>
      <ui.TextField
        name="name"
        label="Name"
        control={control}
        rules={{ required: 'Name is required' }}
      />
      <ui.TextField
        name="email"
        label="Email"
        type="email"
        control={control}
        rules={{
          required: 'Email is required',
          pattern: {
            value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
            message: 'Invalid email address'
          }
        }}
      />
      <ui.SelectField
        name="role"
        label="Role"
        control={control}
        options={[
          { value: 'user', label: 'User' },
          { value: 'admin', label: 'Admin' }
        ]}
      />
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Submitting...' : id ? 'Update User' : 'Create User'}
      </button>
    </form>
  );
}

Advanced Usage

Optimistic Updates

The UI package supports optimistic updates for a better user experience:
import { ui } from '@repo/data';

function ToggleUserStatus({ id, status, onToggle }) {
  const dataProvider = ui.useDataProvider();
  const queryClient = ui.useQueryClient();
  
  const handleToggle = async () => {
    const newStatus = status === 'active' ? 'inactive' : 'active';
    
    // Optimistically update the UI
    queryClient.setQueryData(['users', id], (old) => ({
      ...old,
      status: newStatus
    }));
    
    try {
      // Perform the actual update
      await dataProvider.update({
        resource: 'users',
        id,
        data: { status: newStatus }
      });
      
      // Notify parent component
      onToggle(newStatus);
    } catch (error) {
      // Revert the optimistic update on error
      queryClient.setQueryData(['users', id], (old) => ({
        ...old,
        status
      }));
      
      console.error('Failed to toggle status:', error);
    }
  };
  
  return (
    <button onClick={handleToggle}>
      {status === 'active' ? 'Deactivate' : 'Activate'}
    </button>
  );
}

Custom Hooks

You can create custom hooks that build on the provided hooks:
import { ui } from '@repo/data';

// Custom hook for user management
function useUsers() {
  const { data, loading, error, refetch } = ui.useGetList('users');
  const [createUser] = ui.useCreate('users');
  const [updateUser] = ui.useUpdate('users');
  const [deleteUser] = ui.useDelete('users');
  
  const addUser = async (userData) => {
    await createUser(userData);
    refetch();
  };
  
  const editUser = async (id, userData) => {
    await updateUser(id, userData);
    refetch();
  };
  
  const removeUser = async (id) => {
    await deleteUser(id);
    refetch();
  };
  
  return {
    users: data,
    loading,
    error,
    addUser,
    editUser,
    removeUser
  };
}

// Usage
function UserManagement() {
  const { users, loading, error, addUser, editUser, removeUser } = useUsers();
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <div>
      <h2>Users</h2>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.name}
            <button onClick={() => editUser(user.id, { role: 'admin' })}>
              Make Admin
            </button>
            <button onClick={() => removeUser(user.id)}>
              Delete
            </button>
          </li>
        ))}
      </ul>
      <button onClick={() => addUser({ name: 'New User', role: 'user' })}>
        Add User
      </button>
    </div>
  );
}

API Reference

For a complete API reference, please refer to the TypeScript definitions in the package.