Crud
The Crud component provides UIs to interact with data from any data source.
With the Crud
component and its subcomponents you can easily generate pages where items from your data source can be listed in a table, shown individually in detail, or created and edited with forms. All with minimal configuration from a single data source definition.
Demo
The Crud
component automatically generates CRUD pages for your data source, automatically integrated with your routing solution. All that is required is a dataSource
definition and a rootPath
for the CRUD pages.
The pages are in the following routes:
- List:
/${rootPath}
- Show:
/${rootPath}/:id
- Create:
/${rootPath}/new
- Edit:
/${rootPath}/:id/edit
These default paths and other out-of-the-box settings can be overriden and configured in more detail by following the advanced configuration below.
It is recommended to include the dataSourceCache
prop in order to properly cache results from the data source query methods. See more in the section about data caching below.
Optionally, additional configuration options can be provided such as initialPageSize
for the paginated list of items, or defaultValues
to set the initial form values when using a form to create new items.
Data sources
A data source is an object with a particular shape that must be used to configure the Crud
component or its subcomponents.
It includes:
A
fields
property, which extends the Data Grid column definition where different fields, header names and field types, among others, can be specified for your data. Anid
field (string or number) is mandatory so that individual items can be identified.Properties for methods to interact with data such as
getMany
,getOne
,createOne
,updateOne
anddeleteOne
.A
validate
function for form validation when creating or editing data, which returns validation errors for each field.
Here is a simplified example of a data source definition:
const notesDataSource: DataSource<Note> = {
fields: [
{ field: 'id', headerName: 'ID' },
{ field: 'title', headerName: 'Title' },
{ field: 'text', headerName: 'Text' },
],
getMany: ({ paginationModel }) => {
const start = paginationModel.page * paginationModel.pageSize;
const end = start + paginationModel.pageSize;
const paginatedNotes = notesStore.slice(start, end);
return {
items: paginatedNotes,
itemCount: notesStore.length,
};
},
getOne: (noteId) => {
return notesStore.find((note) => note.id === noteId) ?? null;
},
createOne: (data) => {
const newNote = { id: notesStore.length + 1, ...data } as Note;
notesStore = [...notesStore, newNote];
return newNote;
},
updateOne: (noteId, data) => {
let updatedNote: Note | null = null;
notesStore = notesStore.map((note) => {
if (note.id === noteId) {
updatedNote = { ...note, ...data };
return updatedNote;
}
return note;
});
return updatedNote;
},
deleteOne: (noteId) => {
notesStore = notesStore.filter((note) => note.id !== noteId);
},
validate: (formValues) => {
let issues: { message: string; path: [keyof Note] }[] = [];
if (!formValues.title) {
issues = [...issues, { message: 'Title is required', path: ['title'] }];
}
if (!formValues.text) {
issues = [...issues, { message: 'Text is required', path: ['text'] }];
}
return { issues };
},
};
getMany
The getMany
method in a data source can be used for retrieving paginated, sorted and filtered items to be displayed as a list, usually in a table.
Pagination, sorting, and filtering can be integrated with your data fetching via the paginationModel
, filterModel
and sortModel
arguments. These arguments follow the same shape as the models for pagination, sorting and filtering used by the Data Grid.
This method must return an array of objects with the items to be displayed.
{
//...
getMany: async ({ paginationModel, filterModel, sortModel }) => {
// Fetch data from server
const data = await fetch('https://my-api.com/data', {
method: 'GET',
body: JSON.stringify({
page: paginationModel.page,
pageSize: paginationModel.pageSize,
sortModel,
filterModel,
}),
});
return data
},
// ...
}
getOne
The getOne
method in a data source can be used for retrieving a single specific item and displaying all of its attributes.
This method must return an object corresponding to the item to be displayed.
{
//...
getOne: async (id) => {
// Fetch record from server
const record = await fetch(`https://my-api.com/data/${id}`, {
method: 'GET',
});
return record;
},
// ...
}
createOne
The createOne
method in a data source can be used for creating a new item, usually from a form where the value for each of its attributes can be specified.
This method must return an object corresponding to the new item that has been created.
{
//...
createOne: async (data) => {
// Create record in the server
const record = await fetch('https://my-api.com/data', {
method: 'POST',
body: JSON.stringify(data),
});
return record;
},
// ...
}
updateOne
The updateOne
method in a data source can be used for updating an existing item, usually from a form where the value for each of its attributes can be specified.
This method must return an object corresponding to the new item that has been updated.
{
//...
updateOne: async (id, data) => {
// Update record in the server
const record = await fetch(`https://my-api.com/data/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
});
return record;
},
// ...
}
deleteOne
The deleteOne
method in a data source can be used for deleting an existing item.
This method does not need to return anything.
{
//...
deleteOne: async (id) => {
// Delete record in the server
await fetch(`https://my-api.com/data/${id}`, {
method: 'DELETE',
});
},
// ...
}
Form validation
Optionally, a validate
function can be included in the data source in order to do form validation.
This function follows the Standard Schema, so it takes an object with field values for a given item and must return the corresponding errors for each field, as shown in the example:
{
//...
validate: (formValues) => {
let issues = [];
if (!formValues.name) {
issues = [...issues, { message: 'Name is required', path: ['name'] }];
}
if (formValues.age < 18) {
issues = [...issues, { message: 'Age must be higher than 18', path: ['age'] }];
}
return { issues };
},
// ...
}
This function runs automatically against the current values when the user tries to submit a form inside the Crud
component, or changes any of its fields.
Integration with external schema libraries
The validate
function can easily be integrated with any schema libaries that follow the Standard Schema spec, such as zod
.
Here's an example using zod
:
import { z } from 'zod';
const dataSource = {
// ...
validate: z.object({
name: z
.string({ required_error: 'Name is required' })
.nonempty('Name is required'),
age: z
.number({ required_error: 'Age is required' })
.min(18, 'Age must be higher than 18'),
})['~standard'].validate,
// ...
};
Data caching
Data sources cache fetched data by default. This means that if the user queries data that has already been fetched, query methods such as getMany
and getOne
are not called again to avoid unnecessary calls to the server.
Successfully calling mutation methods such as createOne
, updateOne
or deleteOne
automatically clears the cache for all queries in the same data source.
It is recommended to always include the dataSourceCache
prop in order to cache data at the scope of whole application, otherwise by default the cache is only scoped to the component being used.
Each data source should have its own single cache instance across the whole application.
An instance of DataSourceCache
may be used for caching as seen in the demo above. DataSourceCache
is a simple in-memory cache that stores the data in a plain object.
Disable cache
To disable the data source cache, pass null
to the dataSourceCache
prop.
Advanced configuration
For more flexibility of customization, and especially if you want full control over where to place the different CRUD pages, you can use the List
, Show
, Create
and Edit
subcomponents instead of the all-in-one Crud
component.
The CrudProvider
component is optional, but it can be used to easily pass a single dataSource
and dataSourceCache
to the CRUD subcomponents inside it as context.
Alternatively, each of those components can take its own dataSource
and dataSourceCache
as props.
List
component
Displays a Data Grid listing items from a data source, with support for pagination, sorting and filtering, along with some useful controls such as refreshing data.
If props are passed for onCreateClick
or onEditClick
, buttons are shown for triggering those callbacks.
If the data source includes deleteOne
, it is possible to delete items directly from their respective rows.
Integration with Data Grid
The dataGrid
slot and slot props can be used to replace the standard Data Grid with any of its commercially licensed versions.
Show
component
Displays details for an item with a given id
retrieved from a data source.
If a prop is passed for onEditClick
, a button is shown for triggering that callback.
If the data source includes deleteOne
, it is possible to delete items directly from this view.
Create
component
Displays a form for creating a new item for a data source, automatically generated from the given fields
and field type
s.
The supported field types are string
, number
, boolean
, date
, dateTime
and singleSelect
.
Form validation integrates automatically with the validate
function in the data source.
Edit
component
Displays a form for editing an item with a given id
retrieved from a data source, similarly to the Create
component.
The form fields are prepopulated with the item's attributes.
API
See the documentation below for a complete reference to all of the props and classes available to the components mentioned here.