Seed Protocol
@seedprotocol/react

Models

Hooks to list, load, create and delete the models in a schema from React components.

These hooks return Model instances from @seedprotocol/sdk: the definitions of your content types, such as Post or Author, not the items stored under them. Use them in schema editors and admin screens. To work with items, see Items.

import { useModels, useModel, useCreateModel, useDestroyModel } from '@seedprotocol/react'

The read hooks wait for the client to be ready before they query, and return an empty result with isLoading: false until then. useModels and useModel cache through React Query: outside SeedProvider they use a shared fallback client with Seed's defaults, and development builds log a warning, so keep them inside SeedProvider (which hooks need it). The Model class itself is covered in Model.

useModels and useModel hold the models they show while the component is mounted and release them when it unmounts or shows others. When a shown model is destroyed or evicted, they fetch again: the list drops it, and useModel returns undefined.

Where a hook takes a schema, you can pass the schema's name (its metadata.name, such as 'blog') or its schema file ID.

useModels

Lists the models in a schema and updates when one is added or deleted.

function useModels(schemaId: string | null | undefined): {
  models: Model[]
  isLoading: boolean
  error: Error | null
}

Parameters

NameTypeDescription
schemaIdstring | null | undefinedSchema name or schema file ID. Pass null or undefined to load nothing

Returns

FieldTypeDescription
modelsModel[]The schema's models, including ones still being created. [] until loaded
isLoadingbooleantrue during the first load
errorError | nullThe query's error, if Model.all() failed

Example

import { useModels } from '@seedprotocol/react'

export function ModelList() {
  const { models, isLoading, error } = useModels('blog')
  if (isLoading) return <p>Loading models…</p>
  if (error) return <p>{error.message}</p>
  return (
    <ul>
      {models.map((model) => (
        <li key={model.id ?? model.modelName}>{model.modelName}</li>
      ))}
    </ul>
  )
}

The hook watches the schema's rows in the models table and refetches whenever the set of models changes, deletes included, so deleting the last model empties the list. While the table still has rows, a refetch that briefly returns no models keeps the previous list instead of flashing [], unless a model in that list has stopped.

useModel

Loads one model, either by schema and model name or by model file ID. The component re-renders when the model's state changes.

function useModel(
  schemaIdOrModelId: string | null | undefined,
  modelName?: string | null | undefined,
): {
  model: Model | undefined
  isLoading: boolean
  error: Error | null
}

Parameters

NameTypeDescription
schemaIdOrModelIdstring | null | undefinedWith modelName: the schema name or schema file ID. Without it: the model's file ID
modelNamestring | null | undefinedThe model's name, such as 'Post'. Leave it out (or pass null) to look up by model file ID

The two forms work differently:

CallLookup
useModel('blog', 'Post')Finds Post in useModels('blog'). isLoading and error come from that list
useModel(modelFileId)Model.createById(modelFileId), from cache or the database

Returns

FieldTypeDescription
modelModel | undefinedThe model, or undefined if it isn't found or hasn't loaded
isLoadingbooleantrue while loading
errorError | nullThe lookup error, if any

Example

import { useModel } from '@seedprotocol/react'

export function PostModelStatus() {
  const { model, isLoading } = useModel('blog', 'Post')
  if (isLoading) return null
  return <p>{model ? `Post is ${model.status}` : 'No Post model'}</p>
}

export function ModelById({ modelFileId }: { modelFileId: string }) {
  const { model, error } = useModel(modelFileId)
  if (error) return <p>{error.message}</p>
  return <p>{model?.modelName} in {model?.schemaName}</p>
}

Whether modelName is set decides which form runs, so modelName can switch between null and a string while the component is mounted. While it's null, useModel('blog', selectedName) looks 'blog' up as a model file ID.

useCreateModel

Adds a model to a schema and tracks its progress.

function useCreateModel(): UseCreateModelReturn

type UseCreateModelReturn = {
  create: (schemaName: string, modelName: string, options?: UseCreateModelOptions) => Model
  isLoading: boolean
  error: Error | null
  resetError: () => void
}

type UseCreateModelOptions = {
  modelFileId?: string
  properties?: { [propertyName: string]: any }
}

Both types are exported, as is UseDestroyModelReturn.

Parameters of create

NameTypeDefaultDescription
schemaNamestringThe schema to add the model to
modelNamestringThe new model's name
options.modelFileIdstringA generated 10-character IDUse a known model file ID instead of generating one. If a model with this ID is already loaded, create returns that instance
options.properties{ [name]: definition }{}Property definitions to create with the model, in the code shape, such as { body: { dataType: 'Text' } }. See the property definition reference

Returns

FieldTypeDescription
createfunctionReturns the model's instance synchronously. A new model is still loading
isLoadingbooleantrue from the call until the model's status is 'ready' or 'error'. A model that's already loaded clears it at once, and so does a throw
errorError | nullThe error create threw, or, if the model's status turns 'error', the load's own error (Failed to create model if it isn't an Error)
resetError() => voidClears error

Note the argument order: the hook's create takes the schema first, create(schemaName, modelName), while the SDK's Model.create takes (modelName, schemaName). If either is empty, it sets error and throws Model name is required or Schema name is required.

Example

import { useCreateModel } from '@seedprotocol/react'

export function AddCommentModel() {
  const { create, isLoading, error } = useCreateModel()
  return (
    <>
      <button
        disabled={isLoading}
        onClick={() =>
          create('blog', 'Comment', {
            properties: {
              body: { dataType: 'Text' },
              post: { dataType: 'Relation', ref: 'Post' },
            },
          })
        }
      >
        Add Comment model
      </button>
      {error && <p>{error.message}</p>}
    </>
  )
}

useModels('blog') picks up the new model once it's written to the database.

A name already in the schema gets a number. If the schema already has a model with that name (compared case-insensitively) and you don't pass modelFileId, the SDK doesn't return the existing model. It creates another one with a numbered name: create('blog', 'post') next to Post makes post 1. Check useModels first if you want find-or-create.

useDestroyModel

Deletes a model and its property definitions from the local database. Items of the model aren't deleted; see destroy().

function useDestroyModel(): UseDestroyModelReturn

type UseDestroyModelReturn = {
  destroy: (model: Model) => Promise<void>
  isLoading: boolean
  error: Error | null
  resetError: () => void
}

Returns

FieldTypeDescription
destroy(model: Model) => Promise<void>Calls model.destroy(). Does nothing if model is falsy
isLoadingbooleantrue while destroy runs
errorError | nullSet if model.destroy() rejects
resetError() => voidClears error

Like the other destroy hooks, destroy sets error and rejects when model.destroy() fails, database failures included. Catch the promise.

import type { Model } from '@seedprotocol/sdk'
import { useDestroyModel } from '@seedprotocol/react'

export function DeleteModelButton({ model }: { model: Model }) {
  const { destroy, isLoading, error } = useDestroyModel()
  return (
    <>
      <button disabled={isLoading} onClick={() => destroy(model).catch(() => {})}>
        Delete {model.modelName}
      </button>
      {error && <p>{error.message}</p>}
    </>
  )
}

On this page