Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@devup-api/ui

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@devup-api/ui

latest
npmnpm
Version
0.1.2
Version published
Weekly downloads
12
200%
Maintainers
1
Weekly downloads
 
Created
Source

@devup-api/ui

CRUD UI components for devup-api with automatic mode detection, React Hook Form integration, and OpenAPI-driven configuration.

Installation

npm install @devup-api/ui @devup-api/fetch @tanstack/react-query react-hook-form zod

Features

  • Automatic Mode Detection: Automatically switches between create and edit mode based on provided params
  • OpenAPI Tag Integration: Generates CRUD configurations from OpenAPI tags (devup:{name}:{mode})
  • Headless Mode: Full control with render function pattern
  • Default UI Mode: Quick setup with field configurations
  • React Query Integration: Built-in query and mutation state management
  • React Hook Form: Type-safe form handling with validation
  • Edit Mode Options: Support for both PUT (edit) and PATCH (fix) updates

OpenAPI Tag Configuration

The package uses special OpenAPI tags to define CRUD operations. Tags follow the pattern:

devup:{name}:{mode}

Available Modes

ModeHTTP MethodDescription
oneGETFetch single item (required)
createPOSTCreate new item (required)
editPUTFull update (optional)
fixPATCHPartial update (optional)

Example OpenAPI Spec

paths:
  /users/{id}:
    get:
      operationId: getUser
      tags:
        - devup:user:one
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
    put:
      operationId: updateUser
      tags:
        - devup:user:edit
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
    patch:
      operationId: patchUser
      tags:
        - devup:user:fix
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
  /users:
    post:
      operationId: createUser
      tags:
        - devup:user:create

CRUD Group Requirements

For a valid CRUD group, both one and create modes must be defined. The edit and fix modes are optional.

Virtual Module

CRUD configurations are generated at build time and available via a virtual module:

import { crudConfigs } from '@devup-api/ui/crud'

// Access individual configs
const userConfig = crudConfigs.user

// Generated config structure:
// {
//   name: 'user',
//   one: { method: 'get', path: '/users/{id}', params: ['id'] },
//   create: { method: 'post', path: '/users', params: [] },
//   edit: { method: 'put', path: '/users/{id}', params: ['id'] },
//   fix: { method: 'patch', path: '/users/{id}', params: ['id'] },
// }

Usage

ApiCrud Component

Create Mode (No Params)

import { createApi } from '@devup-api/fetch'
import { ApiCrud } from '@devup-api/ui'
import { crudConfigs } from '@devup-api/ui/crud'

const api = createApi('https://api.example.com')

function CreateUserForm() {
  return (
    <ApiCrud
      config={crudConfigs.user}
      api={api}
      fields={[
        { name: 'name', label: 'Name', type: 'text', required: true },
        { name: 'email', label: 'Email', type: 'email', required: true },
      ]}
      onCreateSuccess={(data) => {
        console.log('User created:', data)
      }}
    />
  )
}

Edit Mode (With Params)

function EditUserForm({ userId }: { userId: string }) {
  return (
    <ApiCrud
      config={crudConfigs.user}
      api={api}
      params={{ id: userId }}
      fields={[
        { name: 'name', label: 'Name', type: 'text', required: true },
        { name: 'email', label: 'Email', type: 'email', required: true },
      ]}
      oneLoading={<div>Loading...</div>}
      oneFallback={<div>User not found</div>}
      onUpdateSuccess={(data) => {
        console.log('User updated:', data)
      }}
    />
  )
}

Using PATCH Instead of PUT

<ApiCrud
  config={crudConfigs.user}
  api={api}
  params={{ id: userId }}
  editMode="fix"  // Use PATCH instead of PUT
  fields={fields}
  onUpdateSuccess={(data) => console.log('Patched:', data)}
/>

Headless Mode (Render Function)

<ApiCrud config={crudConfigs.user} api={api} params={{ id: userId }}>
  {({ form, mode, submit, isLoading, one }) => (
    <form onSubmit={(e) => { e.preventDefault(); submit(); }}>
      <input {...form.register('name')} placeholder="Name" />
      {form.formState.errors.name && (
        <span>{String(form.formState.errors.name.message)}</span>
      )}
      
      <input {...form.register('email')} placeholder="Email" />
      {form.formState.errors.email && (
        <span>{String(form.formState.errors.email.message)}</span>
      )}
      
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Loading...' : mode === 'create' ? 'Create' : 'Save'}
      </button>
    </form>
  )}
</ApiCrud>

Custom Field and Submit Rendering

<ApiCrud
  config={crudConfigs.user}
  api={api}
  fields={[
    { name: 'name', label: 'Name', type: 'text', required: true },
    { name: 'email', label: 'Email', type: 'email', required: true },
  ]}
  renderField={(field, form) => (
    <div key={field.name} className="form-field">
      <label htmlFor={field.name}>{field.label}</label>
      <input
        id={field.name}
        {...form.register(field.name)}
        placeholder={field.placeholder}
        className="input"
      />
    </div>
  )}
  renderSubmit={({ isLoading, mode }) => (
    <button type="submit" disabled={isLoading} className="btn-primary">
      {isLoading ? 'Saving...' : mode === 'create' ? 'Create User' : 'Update User'}
    </button>
  )}
  formProps={{ className: 'user-form' }}
/>

useApiCrud Hook

For complete control, use the hook directly:

import { useApiCrud } from '@devup-api/ui'
import { crudConfigs } from '@devup-api/ui/crud'

function UserForm({ userId }: { userId?: string }) {
  const crud = useApiCrud({
    config: crudConfigs.user,
    api,
    params: userId ? { id: userId } : undefined,
    onCreateSuccess: (data) => console.log('Created:', data),
    onUpdateSuccess: (data) => console.log('Updated:', data),
  })

  if (crud.mode === 'edit' && crud.one.isLoading) {
    return <div>Loading...</div>
  }

  if (crud.mode === 'edit' && crud.one.isError) {
    return <div>Error loading user</div>
  }

  return (
    <form onSubmit={(e) => { e.preventDefault(); crud.submit(); }}>
      <input {...crud.form.register('name')} />
      <input {...crud.form.register('email')} />
      
      <button type="submit" disabled={crud.isLoading}>
        {crud.mode === 'create' ? 'Create' : 'Save'}
      </button>
    </form>
  )
}

Props

ApiCrud Component Props

PropTypeDescription
configCrudConfigCRUD configuration from generated configs
apiDevupApiAPI client instance from @devup-api/fetch
paramsRecord<string, unknown>Path parameters - if provided, enables edit mode
editMode'edit' | 'fix'Which update method to use (PUT or PATCH)
fieldsFieldConfig[]Field configurations for default UI rendering
defaultValuesobjectDefault values for create mode
onCreateSuccess(data) => voidCalled when create succeeds
onUpdateSuccess(data) => voidCalled when edit/fix succeeds
onOneError(error) => voidCalled when GET fails
onCreateError(error) => voidCalled when create fails
onUpdateError(error) => voidCalled when edit/fix fails
oneFallbackReactNodeFallback UI when GET fails
oneLoadingReactNodeLoading UI while GET is loading
childrenReactNode | ((props) => ReactNode)Children or render function for headless mode
renderField(field, form) => ReactNodeCustom field renderer
renderSubmit(props) => ReactNodeCustom submit button renderer
formPropsFormHTMLAttributesHTML form element props
queryClientQueryClientCustom React Query client

FieldConfig Type

PropertyTypeDescription
namestringField name (path in form data)
labelstringDisplay label
typeFieldTypeField type for UI rendering
requiredbooleanWhether the field is required
placeholderstringPlaceholder text
defaultValueunknownDefault value
validationobjectValidation constraints (min, max, minLength, maxLength, pattern, options)
childrenFieldConfig[]Nested fields for object/array types
descriptionstringHelp text

FieldType Values

text | number | email | password | url | tel | textarea | select | checkbox | radio | date | datetime | time | file | hidden | array | object

useApiCrud Return Type

PropertyTypeDescription
mode'create' | 'edit'Current operation mode
oneobjectGET query state (data, isLoading, isError, error, refetch)
createobjectCreate mutation state (mutate, mutateAsync, isPending, isSuccess, isError, error, reset)
updateobjectUpdate mutation state (mutate, mutateAsync, isPending, isSuccess, isError, error, reset)
formUseFormReturnReact Hook Form instance
fieldsFieldConfig[]Field configurations
submit() => voidSubmit handler
isLoadingbooleanWhether any operation is in progress

Type Exports

import type {
  // Component Props
  ApiCrudComponentProps,
  ApiCrudProps,
  ApiCrudRenderProps,
  
  // CRUD Configuration
  CrudConfig,
  CrudConfigs,
  CrudEndpoint,
  CrudMode,
  EditMode,
  
  // Field Configuration
  FieldConfig,
  FieldType,
  
  // Hook Types
  UseApiCrudOptions,
  UseApiCrudReturn,
  UseApiCrudHookOptions,
  UseApiCrudResult,
  
  // Module Augmentation
  DevupCrudConfigs,
} from '@devup-api/ui'

Utility Functions

analyzeSchema

Analyze a Zod schema and extract field configurations:

import { analyzeSchema } from '@devup-api/ui'
import { z } from 'zod'

const userSchema = z.object({
  name: z.string(),
  email: z.string().email(),
  age: z.number().optional(),
})

const fields = analyzeSchema(userSchema)
// [
//   { name: 'name', label: 'Name', type: 'text', required: true },
//   { name: 'email', label: 'Email', type: 'email', required: true },
//   { name: 'age', label: 'Age', type: 'number', required: false },
// ]

getDefaultValues

Get default values from field configurations:

import { getDefaultValues } from '@devup-api/ui'

const defaults = getDefaultValues(fields)
// { age: 0 }

License

Apache 2.0

FAQs

Package last updated on 04 Apr 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts