resourceui

Getting Started

Install

npm install @ferrumec/resourceui

resourceui expects React and ReactDOM 18+ as peer dependencies (already in most projects). If you plan to use the /router entry point, also install react-router-dom 6+ — it’s optional otherwise.

npm install react react-dom
# optional, only if you use @ferrumec/resourceui/router
npm install react-router-dom

Pull in the default stylesheet if you’re using the built-in layouts as-is:

import "@ferrumec/resourceui/theme.css";

You can skip this entirely if you’re supplying your own layout components (see Tutorials).

The three things you always need

Every screen in resourceui is built from the same three ingredients:

  1. A type describing one record, e.g. interface User { id: number; name: string; email: string; }.
  2. Field metadata — which properties to show, and how (FieldMetadata<T>[]).
  3. An API — functions that actually talk to your backend (ResourceApi<T> for a collection, ItemApi<T> for a single record).

Hand-write all three for full control, or generate (2) and (3) from a schema — covered in the tutorials. This guide hand-writes them so the moving parts are visible.

Step 1 — Define your type

export interface User {
  id: number;
  name: string;
  email: string;
  isActive: boolean;
}

Step 2 — Build a ResourceApi

The fastest way to wire a ResourceApi<T> against a REST backend is createRestResourceApi, which expects your list endpoint to return { items, total, page, pageSize }:

import { createRestResourceApi } from "@ferrumec/resourceui";

const usersApi = createRestResourceApi<User>("/api/users");

This single call gives you list, create, and itemApi(id) — the full ResourceApi<User> contract — all pointed at /api/users.

Step 3 — Describe the fields

import type { FieldMetadata } from "@ferrumec/resourceui";

const userFields: FieldMetadata<User>[] = [
  { name: "id", type: "number", editable: false },
  { name: "name", type: "text", required: true, sortable: true },
  { name: "email", type: "email", required: true, filterable: true },
  { name: "isActive", label: "Active", type: "boolean" },
];

Step 4 — Create the Resource

import { Resource } from "@ferrumec/resourceui";

export const users = Resource<User>({
  name: "user",
  fields: userFields,
  api: usersApi,
});

Resource(...) returns a ResourceController<User> — a plain object, not a component. Create it once (module scope, or memoized in a parent component) and reuse it anywhere you need a users view; every view stays in sync automatically.

Step 5 — Render a list

function UsersPage() {
  return (
    <div>
      <h1>Users</h1>
      {users.ListView({
        onSelect: (id) => console.log("selected user", id),
      })}
    </div>
  );
}

That’s a working paginated, sortable table: it fetches on mount, re-fetches when you sort a column or change page, and shows a delete button per row using the default layout.

Step 6 — Add create, detail, and update

function UserCreatePage() {
  return users.CreateView({
    onCreated: (user) => console.log("created", user.id),
  });
}

function UserDetailPage({ id }: { id: number }) {
  const item = users.item(id); // cached ItemController, shares data with the list
  return item.DetailView({
    onEdit: () => console.log("go to edit screen"),
    onDelete: () => console.log("deleted"),
  });
}

function UserEditPage({ id }: { id: number }) {
  const item = users.item(id);
  return item.UpdateView({
    onUpdated: (user) => console.log("saved", user),
  });
}

users.item(id) is cached per id — calling it repeatedly for the same id returns the same ItemController, and it’s kept in sync whenever the list refetches or a row is edited/deleted.

A minimal complete example

import { Resource, createRestResourceApi, type FieldMetadata } from "@ferrumec/resourceui";
import "@ferrumec/resourceui/theme.css";

interface User {
  id: number;
  name: string;
  email: string;
  isActive: boolean;
}

const userFields: FieldMetadata<User>[] = [
  { name: "id", type: "number", editable: false },
  { name: "name", type: "text", required: true, sortable: true },
  { name: "email", type: "email", required: true },
  { name: "isActive", label: "Active", type: "boolean" },
];

const users = Resource<User>({
  name: "user",
  fields: userFields,
  api: createRestResourceApi<User>("/api/users"),
});

export default function App() {
  return (
    <div>
      <h1>Users</h1>
      {users.CreateView({})}
      {users.ListView({})}
    </div>
  );
}

Where to go from here