A comprehensive React component library for building dynamic, schema-driven entity management interfaces. Built with @simtlix/simfinity-js-client and Material-UI, this package provides powerful components that automatically generate forms and tables from GraphQL schema introspection.
- Dynamic Form Generation: Automatically generates forms from GraphQL schema introspection
- Advanced Data Tables: Server-side pagination, sorting, filtering with Material-UI DataGrid
- Entity Management: Create, edit, view, and manage any entity type
- Stepper Mode: Multi-step forms with customizable steps and navigation
- Complex Relationships: Handles nested objects, collections, and foreign key relationships
- Extensive Customization: Field-level customization with visibility, validation, and layout control
- Internationalization: Built-in i18n support with multi-language capabilities
- State Machine Integration: Built-in support for entity state machines
- Responsive Design: Beautiful, responsive UI components with Material-UI
- TypeScript Support: Full TypeScript support with comprehensive type definitions
- Collection Management: Advanced collection field handling with add/edit/delete operations
npm install @simtlix/simfinity-fe-components @simtlix/simfinity-js-clientThis package requires the following peer dependencies:
npm install @emotion/react @emotion/styled @mui/material @mui/icons-material @mui/system @mui/x-data-grid react react-domimport { ThemeProvider } from '@mui/material/styles';
import { CssBaseline } from '@mui/material';
import {
SimfinityClientProvider,
I18nProvider,
EntityForm,
EntityTable
} from '@simtlix/simfinity-fe-components';
function MyApp() {
return (
<SimfinityClientProvider endpoint="http://localhost:3000/graphql">
<ThemeProvider theme={theme}>
<CssBaseline />
<I18nProvider>
<EntityTable listField="series" />
<EntityForm listField="series" action="create" />
</I18nProvider>
</ThemeProvider>
</SimfinityClientProvider>
);
}SimfinityClientProvider initializes the SimfinityClient, performs a single introspection query against the Simfinity backend, and makes the client available to all child components via React context.
You can customize the loading and error states:
<SimfinityClientProvider
endpoint="http://localhost:3000/graphql"
loadingFallback={<MyCustomSpinner />}
errorFallback={(error) => <MyErrorPage message={error.message} />}
>
{/* ... */}
</SimfinityClientProvider>// app/page.tsx
'use client';
import { EntityTable } from '@simtlix/simfinity-fe-components';
import { useRouter, useSearchParams } from 'next/navigation';
import { useCallback } from 'react';
export default function MyPage() {
const router = useRouter();
const searchParams = useSearchParams();
const navigate = useCallback((path: string) => {
router.push(path);
}, [router]);
const getSearchParams = useCallback(() => {
return searchParams;
}, [searchParams]);
const onSearchParamsChange = useCallback((params: URLSearchParams) => {
const newUrl = `${window.location.pathname}?${params.toString()}`;
router.replace(newUrl);
}, [router]);
return (
<EntityTable
listField="series"
onNavigate={navigate}
getSearchParams={getSearchParams}
onSearchParamsChange={onSearchParamsChange}
/>
);
}A powerful data grid component with server-side pagination, sorting, and filtering.
<EntityTable
listField="series" // GraphQL list field name
onNavigate={(path) => router.push(path)} // Optional: custom navigation
getSearchParams={() => searchParams} // Optional: custom URL params
onSearchParamsChange={(params) => updateURL(params)} // Optional: custom URL updates
/>Features:
- Server-side pagination, sorting, and filtering
- Automatic column generation from GraphQL schema
- Custom column renderers
- State machine field support
- URL state synchronization
- Responsive design
A card-based list view with the same filtering, pagination, and sort capabilities as EntityTable. Each card is rendered via a callback that receives the item and a reload function (e.g., to refetch after deleting an item).
<EntityCardList
listField="series"
renderCard={(item, reload) => (
<Card key={item.id}>
<CardContent>{item.name}</CardContent>
<Button onClick={() => deleteItem(item.id).then(reload)}>Delete</Button>
</Card>
)}
getSearchParams={() => new URLSearchParams(window.location.search)}
onSearchParamsChange={(params) => router.replace(`${window.location.pathname}?${params}`)}
onNavigate={(path) => router.push(path)}
showFilterPanel={true}
/>Features:
- Same filtering, pagination, and sort as EntityTable
- Pagination label uses
grid.pagination.cardsPerPage(default: "Cards per page:") — override via i18n JSON orregisterFunctionLabels - Built-in filter panel (StandaloneFilterPanel) and sort dropdown
- URL state synchronization for sharing
renderCard(item, reload, onNavigate?)—reloadtriggers list refetch;onNavigate(when provided) includes returnTo for form Cancel/SuccessshowFilterPanel— set tofalsewhen using external filter UI
Automatically generates forms from GraphQL schema with full CRUD operations.
<EntityForm
listField="series" // GraphQL list field name
action="create" // "create" | "edit" | "view"
entityId="123" // Required for edit/view modes
onNavigate={(path) => router.push(path)} // Optional: custom navigation
returnTo="/entities/series?page=2" // Optional: where to go on Cancel/Success
/>Features:
- Automatic form generation from schema
- Field validation and error handling
- Collection field management
- State machine integration
- Form customization support
- Breadcrumb navigation
returnTo— when provided, Cancel and default Success navigate here instead of the entity list (EntityTable and EntityCardList pass this via URL when opening forms)
Advanced collection field management with inline editing.
<CollectionFieldGrid
fieldName="episodes"
entityTypeName="Series"
collectionItems={items}
onCollectionChange={handleChange}
customizationState={customizationState}
/>Features:
- Inline add/edit/delete operations
- Status tracking (added/modified/deleted)
- Custom field renderers
- Validation support
- Responsive grid layout
Generic component for rendering any form field type. Uses the SimfinityClient internally via useSimfinityClient() for schema metadata.
<FormFieldRenderer
field={field} // Field definition from schema
value={value} // Current field value
onChange={handleChange} // Change handler
error={error} // Validation error
entityTypeName="Series" // Entity type name
customizationState={customizationState} // Optional customization
/>Supported Field Types:
- Text fields (string, number, email, etc.)
- Boolean fields (checkboxes)
- Date/time fields
- Enum fields (select dropdowns)
- Object fields (nested forms)
- List fields (collections)
- State machine fields
The library provides data-fetching hooks that wrap the SimfinityClient imperative API in reactive React hooks:
Manages URL-synced pagination, sort, and filter state for list views. Used internally by EntityTable and EntityCardList; also available for custom list UIs.
import { useEntityListState } from '@simtlix/simfinity-fe-components';
const {
page,
setPage,
rowsPerPage,
setRowsPerPage,
sortModel,
setSortModel,
filterModel,
setFilterModel,
pendingFilterModel,
setPendingFilterModel,
filterItems,
sortTerms,
updateURL,
} = useEntityListState({
getSearchParams: () => new URLSearchParams(window.location.search),
onSearchParamsChange: (params) => window.history.replaceState({}, '', `?${params}`),
});Access the SimfinityClient instance from context.
import { useSimfinityClient } from '@simtlix/simfinity-fe-components';
function MyComponent() {
const client = useSimfinityClient();
const typeName = client.getTypeNameForQuery('series'); // "Serie"
const fields = client.getFieldsOfType('Serie');
}Paginated list queries with filter and sort support. Re-fetches when page, size, sort, or filter options change.
import { useFind } from '@simtlix/simfinity-fe-components';
const { data, loading, error, totalCount, refetch } = useFind('Serie', {
page: 0,
size: 10,
sort: [{ field: 'name', order: 'ASC' }],
filters: [{ field: 'name', operator: 'LIKE', value: 'Breaking' }],
});Fetch a single entity by ID. Pauses automatically when id is falsy.
import { useEntityById } from '@simtlix/simfinity-fe-components';
const { data, loading, error, refetch } = useEntityById('Serie', entityId, 'id name description');Fetch collection items filtered by a parent entity. Supports excluding IDs (for modified/deleted items during editing).
import { useFindByParent } from '@simtlix/simfinity-fe-components';
const { data, loading, error, totalCount, refetch } = useFindByParent(
'Episode',
'season', // connection field
seasonId, // parent entity ID
{ page: 0, size: 10, excludeIds: ['id1', 'id2'] }
);FK search-as-you-type queries. Pauses when the search term is too short.
import { useSearch } from '@simtlix/simfinity-fe-components';
const { data, loading, error } = useSearch('Director', searchTerm, {
displayField: 'name',
page: 1,
size: 10,
pause: searchTerm.length < 1,
});The package provides extensive customization capabilities through the FormCustomization system:
import { registerFormCustomization } from '@simtlix/simfinity-fe-components';
registerFormCustomization('Episode', 'edit', {
fieldsCustomization: {
name: {
size: { xs: 12, md: 6 },
order: 1,
},
description: {
visible: (field, value, formData) => !!formData.name,
order: 2,
},
genre: {
onChange: (field, value, formData, setFieldData) => {
return { value };
},
},
},
});registerFormCustomization('Serie', 'edit', {
fieldsCustomization: {
seasons: {
onDelete: async (item, setMessage) => {
return true; // confirm deletion
},
onRestore: async (item, status, setMessage) => {
// status: 'deleted' (un-delete), 'modified' (revert), 'added' (remove new)
return true; // return false to prevent the restore/revert/remove
},
onBeforeEdit: async (item, setMessage, parentFormData) => {
return true; // return false to prevent the edit dialog from opening
},
onBeforeCreate: async (setMessage, parentFormData) => {
return true; // return false to prevent the create dialog from opening
},
onEdit: {
fieldsCustomization: { number: { enabled: false } },
onSubmit: async (item, setFieldData, formData) => true,
},
onCreate: {
fieldsCustomization: { number: { order: 1 } },
},
},
},
});registerFormCustomization('Serie', 'create', {
beforeSubmit: async (formData, collectionChanges, transformedData, actions) => {
if (!formData.name) {
actions.setError('Name is required');
return false;
}
return true;
},
onSuccess: (result, formData, collectionChanges, transformedData, actions) => {
return {
message: 'Entity created successfully!',
navigateTo: '/entities/series',
};
},
onError: (error, formData, collectionChanges, transformedData, actions) => {
actions.setError('An error occurred while saving');
},
});registerFormCustomization('Order', 'create', {
mode: 'stepper',
steps: [
{
stepId: 'basics',
stepLabel: 'form.step.basics',
onNext: async (formData, collections, transformed, actions) => {
return true; // validate before moving to next step
},
},
{ stepId: 'details', stepLabel: 'form.step.details' },
{
stepId: 'confirm',
stepLabel: 'form.step.confirm',
customStepRenderer: (actions, handleFieldChange, handleEmbedded, disabled, formData) => (
<ConfirmPage />
),
},
],
fieldsCustomization: {
name: { stepId: 'basics', order: 1 },
category: { stepId: 'basics', order: 2 },
description: { stepId: 'details', order: 1 },
},
});State machines allow you to manage entity state transitions with custom validation and business logic.
import { registerEntityStateMachine } from '@simtlix/simfinity-fe-components';
registerEntityStateMachine('season', {
actions: {
activate: {
mutation: 'activate_season',
from: 'SCHEDULED',
to: 'ACTIVE',
onBeforeSubmit: async (formData, collectionChanges, transformedData, actions) => {
const episodesChanges = collectionChanges.episodes || { added: [], modified: [], deleted: [] };
if (episodesChanges.added.length === 0) {
actions.setFormMessage({
type: 'error',
message: 'Cannot activate season without episodes',
});
return { shouldProceed: false, error: 'Season must have at least one episode' };
}
return { shouldProceed: true };
},
onSuccess: async (result, formData, collectionChanges, transformedData, actions) => {
actions.setFormMessage({ type: 'success', message: 'Season activated successfully!' });
},
onError: async (error, formData, collectionChanges, transformedData, actions) => {
actions.setFormMessage({ type: 'error', message: `Failed to activate: ${error.message}` });
},
},
finalize: {
mutation: 'finalize_season',
from: 'ACTIVE',
to: 'FINISHED',
},
},
});State Machine Configuration:
actions: Object containing all available state transitionsmutation: The mutation name registered in the Simfinity backend for the transitionfrom: Source stateto: Destination stateonBeforeSubmit: Validation callback before transition (return{ shouldProceed: true/false })onSuccess: Callback after successful transitiononError: Callback on transition failure
Integration with EntityForm:
The EntityForm automatically:
- Shows "Actions" button in edit mode for entities with registered state machines
- Displays available actions based on current entity state
- Excludes state machine fields from create forms
- Shows state machine fields as read-only
- Reloads entity data after successful transitions
i18n Labels for State Machines:
{
"stateMachine.season.action.activate": "Activate",
"stateMachine.season.action.finalize": "Finalize",
"stateMachine.season.state.SCHEDULED": "Scheduled",
"stateMachine.season.state.ACTIVE": "Active",
"stateMachine.season.state.FINISHED": "Finished",
"stateMachine.actions": "Actions"
}Built-in i18n support with multiple configuration options:
import { I18nProvider } from '@simtlix/simfinity-fe-components';
function App() {
return (
<SimfinityClientProvider endpoint="http://localhost:3000/graphql">
<I18nProvider>
<EntityForm listField="series" action="create" />
</I18nProvider>
</SimfinityClientProvider>
);
}import { registerFunctionLabels } from '@simtlix/simfinity-fe-components';
registerFunctionLabels('en', {
'entity.series.single': () => 'Series',
'entity.series.plural': () => 'Series',
'entity.series.name': ({ entity }) => `${entity} Name`,
'form.create': ({ entity }) => `Create ${entity}`,
'form.edit': ({ entity }) => `Edit ${entity}`,
'actions.view': ({ entity }) => `View ${entity}`,
'actions.edit': ({ entity }) => `Edit ${entity}`,
'actions.delete': ({ entity }) => `Delete ${entity}`,
});Or use JSON labels in public/i18n/en.json:
{
"entity.series.single": "Series",
"entity.series.plural": "Series",
"entity.series.name": "Series Name",
"form.create": "Create Series",
"form.edit": "Edit Series",
"actions.view": "View",
"actions.edit": "Edit",
"actions.delete": "Delete",
"grid.pagination.rowsPerPage": "Rows per page:",
"grid.pagination.cardsPerPage": "Cards per page:"
}import { registerColumnRenderer } from '@simtlix/simfinity-fe-components';
registerColumnRenderer('series.name', ({ value, row }) => (
<Typography variant="h6" color="primary">
{value}
</Typography>
));
registerColumnRenderer('series.status', ({ value, entity }) => {
const stateKey = `stateMachine.${entity.toLowerCase()}.state.${value}`;
return <Chip label={resolveLabel([stateKey], { entity }, value)} />;
});Full TypeScript support with comprehensive type definitions:
import type {
FormCustomization,
FormField,
CollectionFieldState,
EntityFormCallbacks,
FormMessage,
FieldSize,
MessageType,
FormCustomizationState,
FormCustomizationActions,
ParentFormAccess,
} from '@simtlix/simfinity-fe-components';import { useCollectionState } from '@simtlix/simfinity-fe-components';
function MyComponent() {
const {
collectionStates,
updateCollectionState,
getCollectionState,
resetCollectionState,
getCollectionChanges,
} = useCollectionState();
const handleCollectionChange = (fieldName: string, newState: CollectionFieldState) => {
updateCollectionState(fieldName, newState);
};
return (
<CollectionFieldGrid
fieldName="episodes"
entityTypeName="Series"
collectionItems={getCollectionState('episodes')}
onCollectionChange={handleCollectionChange}
/>
);
}import {
TagsFilterInput,
BetweenFilterInput,
DateFilterInput,
StateMachineFilterInput,
} from '@simtlix/simfinity-fe-components';
// Custom filter inputs are automatically used by EntityTable.
// No additional configuration needed -- they are integrated into the filtering system.// EntityTable automatically handles:
// - Server-side pagination
// - Server-side sorting
// - Server-side filtering
// - URL state synchronization
// - Loading states
// - Error handling
<EntityTable
listField="series"
// All server operations are handled automatically
// based on your GraphQL schema
/>| Prop | Type | Required | Description |
|---|---|---|---|
listField |
string |
Yes | GraphQL list field name (e.g., "series") |
onNavigate |
(path: string) => void |
No | Custom navigation function |
getSearchParams |
() => URLSearchParams |
No | Custom URL params getter |
onSearchParamsChange |
(params: URLSearchParams) => void |
No | Custom URL params updater |
| Prop | Type | Required | Description |
|---|---|---|---|
listField |
string |
Yes | GraphQL list field name |
renderCard |
(item: T, reload: () => void) => React.ReactNode |
Yes | Renders each card; reload refetches the list |
getSearchParams |
() => URLSearchParams |
No | Custom URL params getter |
onSearchParamsChange |
(params: URLSearchParams) => void |
No | Custom URL params updater |
onNavigate |
(path: string) => void |
No | Custom navigation function |
showFilterPanel |
boolean |
No | Show built-in filter panel (default: true) |
| Prop | Type | Required | Description |
|---|---|---|---|
listField |
string |
Yes | GraphQL list field name |
action |
"create" | "edit" | "view" |
Yes | Form action mode |
entityId |
string |
No | Required for edit/view modes |
onNavigate |
(path: string) => void |
No | Custom navigation function |
| Prop | Type | Required | Description |
|---|---|---|---|
endpoint |
string |
Yes | Simfinity GraphQL endpoint URL |
children |
React.ReactNode |
Yes | Child components |
loadingFallback |
React.ReactNode |
No | Custom UI to display while the client initializes |
errorFallback |
(error: Error) => React.ReactNode |
No | Render function for custom error UI on initialization failure |
- Install the package and dependencies:
npm install @simtlix/simfinity-fe-components @simtlix/simfinity-js-client
npm install @emotion/react @emotion/styled @mui/material @mui/icons-material @mui/system @mui/x-data-grid react react-dom- Wrap your app with providers:
import { SimfinityClientProvider, I18nProvider } from '@simtlix/simfinity-fe-components';
import { ThemeProvider } from '@mui/material/styles';
function App() {
return (
<SimfinityClientProvider endpoint="http://localhost:3000/graphql">
<ThemeProvider theme={theme}>
<I18nProvider>
{/* Your app components */}
</I18nProvider>
</ThemeProvider>
</SimfinityClientProvider>
);
}- Start using components:
import { EntityTable, EntityForm } from '@simtlix/simfinity-fe-components';
// Components automatically generate from your GraphQL schemaIf you are upgrading from a version that used URQL:
- Remove URQL dependencies:
npm uninstall urql graphql-tag graphql- Replace the URQL provider with
SimfinityClientProvider:
- import { Provider as UrqlProvider, createClient } from 'urql';
+ import { SimfinityClientProvider } from '@simtlix/simfinity-fe-components';
- const urqlClient = createClient({ url: 'http://localhost:3000/graphql' });
function App() {
return (
- <UrqlProvider value={urqlClient}>
+ <SimfinityClientProvider endpoint="http://localhost:3000/graphql">
<ThemeProvider theme={theme}>
<I18nProvider>
{/* ... */}
</I18nProvider>
</ThemeProvider>
- </UrqlProvider>
+ </SimfinityClientProvider>
);
}- Update introspection utility imports. Functions like
getTypeByName,buildSelectionSetForObjectType,getListEntityFieldNames, andunwrapNamedTypehave been removed. Use the equivalent methods on theSimfinityClientinstance obtained viauseSimfinityClient():
| Removed function | Replacement |
|---|---|
getElementTypeNameOfListField(schema, field) |
client.getTypeNameForQuery(field) |
buildSelectionSetForObjectType(schema, type) |
client.buildSelectionSet(type) |
getListEntityFieldNames(schema) |
client.getListEntityNames() |
isNumericScalarName(name) |
client.isNumericScalar(name) |
isBooleanScalarName(name) |
client.isBooleanScalar(name) |
isDateTimeScalarName(name) |
client.isDateTimeScalar(name) |
getTypeByName(schema, name) |
client.getFieldsOfType(name) |
- Form Customization Guide - Complete guide for customizing forms, fields, collections, and validation
- Navigation Guide - Complete guide for navigation and URL handling
- TypeScript Definitions - Full TypeScript definitions
- Examples Repository - Complete usage examples
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our GitHub repository.
MIT License - see LICENSE file for details.
For support and questions:
- Open an issue on our GitHub repository
- Check the documentation
- Join our community discussions