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).
Every screen in resourceui is built from the same three ingredients:
interface User { id: number; name: string; email: string; }.FieldMetadata<T>[]).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.
export interface User {
id: number;
name: string;
email: string;
isActive: boolean;
}
ResourceApiThe 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.
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" },
];
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.
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.
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.
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>
);
}
FieldMetadata — see Deriving fields from JSON Schema.