Mixing Remote and Local Data Models

As you further develop your admin interface, you may want some of the benefits of remote data models, but also want some of the customizability of local data models. Luckily, this isn't an either/or proposition, and there's a few steps of abstraction you can take to slowly move from one extreme to the other.

Also keep in mind that it is possible to customize the individual List and Detail pages, and that may prove to be a better solution depending on your aims.

Step 1: Remote Data Model

See here for more details.

// In `src/datamodels.tsx`:
const { fetchRemoteDataModels } = useGenerateRemoteDataModelsTRPCClient(/* ... */);

<AdminContextProvider>
  <DataModels fetchRemoteDataModels={fetchRemoteDataModels}>
    <RemoteDataModels />

    {children}
  </DataModels>
</AdminContextProvider>

Step 2: Remote Data Model, but with the ability to customize DataModel props

// In `src/datamodels.tsx`:
const { getPropsForRemoteDataModel } = useGenerateRemoteDataModelsTRPCClient(/* ... */);

<AdminContextProvider>
  <DataModels fetchRemoteDataModels={fetchRemoteDataModels}>
    {/* 1. Remove the "vehicle" remote data model from the auto generated list */}
    <RemoteDataModels exclude={["Vehicle"]} />

    {/* 2. Manually generate the data model */}
    <DataModel<Vehicle>
      name="Vehicle" // NOTE that this must be the same as the name the remote data model had!
      singularDisplayName="Vehicle"
      pluralDisplayName="Vehicles"

      {/* 3. Auto generated the props that do the data fetching - include create/update/delete */}
      {...getPropsForRemoteDataModel('Vehicle', ['createItem', 'updateItem', 'deleteItem'])}

      keyGenerator={vehicle => vehicle.id}
      detailLinkGenerator={vehicle => ({ type: 'href' as const, href: `/admin/vehicles/${vehicle.id}` })}
      listLink={{ type: 'href' as const, href: `/admin/vehicles` }}
      createLink={{ type: 'href', href: `/admin/vehicles/new` }}
    >
      {/* 4. Use all the existing remote field definitions */}
      <RemoteFields name="Vehicle" />
    </DataModel>

    {children}
  </DataModels>
</AdminContextProvider>

Step 3: Remote Data Model, but with custom DataModel supporting overridden custom fields

// In `src/datamodels.tsx`:
const { getPropsForRemoteDataModel } = useGenerateRemoteDataModelsTRPCClient(/* ... */);

<AdminContextProvider>
  <DataModels fetchRemoteDataModels={fetchRemoteDataModels}>
    {/* 1. Remove the "vehicle" remote data model from the auto generated list */}
    <RemoteDataModels exclude={["Vehicle"]} />

    {/* 2. Manually generate the data model */}
    <DataModel<Vehicle>
      name="Vehicle" // NOTE that this must be the same as the name the remote data model had!
      singularDisplayName="Vehicle"
      pluralDisplayName="Vehicles"

      {/* 3. Auto generated the props that do the data fetching - include create/update/delete */}
      {...getPropsForRemoteDataModel('Vehicle', ['createItem', 'updateItem', 'deleteItem'])}

      keyGenerator={vehicle => vehicle.id}
      detailLinkGenerator={vehicle => ({ type: 'href' as const, href: `/admin/vehicles/${vehicle.id}` })}
      listLink={{ type: 'href' as const, href: `/admin/vehicles` }}
      createLink={{ type: 'href', href: `/admin/vehicles/new` }}
    >
      {/* 4. Use all the existing remote field definitions, EXCEPT for one: */}
      <RemoteFields
        name="Vehicle"
        exclude={["metadata"]}
      />

      {/* 5. Define the custom field: */}
      <JSONField<Vehicle, 'metadata'>
        name="metadata"
        singularDisplayName="Metadata"
        pluralDisplayName="Metadata"
        getInitialStateWhenCreating={() => ({ my_default: 'metadata here' })}
      />

      {/* Note that to get the fields in the right order, you may need to put a RemoteFields before
      the field you are adding including the fields before the field you want, and then a
      RemoteFields after including the fields you want after */}
    </DataModel>

    {children}
  </DataModels>
</AdminContextProvider>

Step 4: Remote Data Model, but with fully custom DataModel fields:

The only thing that makes this data model "remote" is that it still relys on the server for data fetching.

// In `src/datamodels.tsx`:
const { getPropsForRemoteDataModel } = useGenerateRemoteDataModelsTRPCClient(/* ... */);

<AdminContextProvider>
  <DataModels fetchRemoteDataModels={fetchRemoteDataModels}>
    {/* 1. Remove the "vehicle" remote data model from the auto generated list */}
    <RemoteDataModels exclude={["Vehicle"]} />

    {/* 2. Manually generate the data model */}
    <DataModel<Vehicle>
      name="Vehicle" // NOTE that this must be the same as the name the remote data model had!
      singularDisplayName="Vehicle"
      pluralDisplayName="Vehicles"

      {/* 3. Auto generated the props that do the data fetching - include create/update/delete */}
      {...getPropsForRemoteDataModel('Vehicle', ['createItem', 'updateItem', 'deleteItem'])}

      keyGenerator={vehicle => vehicle.id}
      detailLinkGenerator={vehicle => ({ type: 'href' as const, href: `/admin/vehicles/${vehicle.id}` })}
      listLink={{ type: 'href' as const, href: `/admin/vehicles` }}
      createLink={{ type: 'href', href: `/admin/vehicles/new` }}
    >
      {/* 5. Define all custom fields - note that there is no RemoteFields component below! */}
      <PrimaryKeyField<Vehicle, 'id'>
        name="id"
        singularDisplayName="Id"
        pluralDisplayName="Ids"
        sortable
      />

      <BooleanField<Vehicle, 'hasBrakes'>
        name="hasBrakes"
        singularDisplayName="Has Brakes"
        pluralDisplayName="Has Brakes"
      />

      <ChoiceField<Vehicle, 'type'>
        name="type"
        singularDisplayName="Car Type"
        pluralDisplayName="Car Types"

        getInitialStateWhenCreating={() => 'unset'}

        choices={[
          {id: 'Unset', disabled: true, label: 'Unset'},
          {id: 'car', label: 'Car'},
          {id: 'truck', label: 'Truck'},
        ]}
      />

      <NumberField<Vehicle, 'numberOfWheels', true>
        name="numberOfWheels"
        singularDisplayName="Number of Wheels"
        pluralDisplayName="Number of Wheels"
        nullable
        getInitialStateWhenCreating={() => null}
      />

      <JSONField<Vehicle, 'metadata'>
        name="metadata"
        singularDisplayName="Metadata"
        pluralDisplayName="Metadata"
        getInitialStateWhenCreating={() => ({ my_default: 'metadata here' })}
      />
    </DataModel>

    {children}
  </DataModels>
</AdminContextProvider>

Step 5: Local Data Model

// In `src/datamodels.tsx`:
<AdminContextProvider>
  <DataModels fetchRemoteDataModels={fetchRemoteDataModels}>
    {/* 1. Remove the "vehicle" remote data model from the auto generated list */}
    <RemoteDataModels exclude={["Vehicle"]} />

    {/* 2. Manually generate the data model */}
    <DataModel<Vehicle>
      name="Vehicle" // NOTE that this must be the same as the name the remote data model had!
      singularDisplayName="Vehicle"
      pluralDisplayName="Vehicles"

      {/* 3. Define fully custom data fetching */}
      {/* More info: https://bread-1.gitbook.io/react-admin-contraption/data-models/datamodel */}
      fetchPageOfData={/* ... */ }
      fetchItem={/* ... */ }
      // createItem
      // updateItem
      // deleteItem

      keyGenerator={vehicle => vehicle.id}
      detailLinkGenerator={vehicle => ({ type: 'href' as const, href: `/admin/vehicles/${vehicle.id}` })}
      listLink={{ type: 'href' as const, href: `/admin/vehicles` }}
      createLink={{ type: 'href', href: `/admin/vehicles/new` }}
    >
      <PrimaryKeyField<Vehicle, 'id'>
        name="id"
        singularDisplayName="Id"
        pluralDisplayName="Ids"
        sortable
      />

      <BooleanField<Vehicle, 'hasBrakes'>
        name="hasBrakes"
        singularDisplayName="Has Brakes"
        pluralDisplayName="Has Brakes"
      />

      <ChoiceField<Vehicle, 'type'>
        name="type"
        singularDisplayName="Car Type"
        pluralDisplayName="Car Types"

        getInitialStateWhenCreating={() => 'unset'}

        choices={[
          {id: 'Unset', disabled: true, label: 'Unset'},
          {id: 'car', label: 'Car'},
          {id: 'truck', label: 'Truck'},
        ]}
      />

      <NumberField<Vehicle, 'numberOfWheels', true>
        name="numberOfWheels"
        singularDisplayName="Number of Wheels"
        pluralDisplayName="Number of Wheels"
        nullable
        getInitialStateWhenCreating={() => null}
      />

      <JSONField<Vehicle, 'metadata'>
        name="metadata"
        singularDisplayName="Metadata"
        pluralDisplayName="Metadata"
        getInitialStateWhenCreating={() => ({ my_default: 'metadata here' })}
      />
    </DataModel>

    {children}
  </DataModels>
</AdminContextProvider>

Last updated