Hatchify gives you the option to customize your list with two compound components that can be nested within your DataGrid
component: DataGrid.Column
, and DataGrid.Empty
.
Let's start with DataGrid.Empty
.
✏️ Update /src/App.tsx
to the following:
// hatchify-app/frontend/App.tsx
import { useState } from "react"
import { hatchifyReact, HatchifyProvider, createJsonapiClient } from "@hatchifyjs/react"
import { createTheme, ThemeProvider } from "@mui/material"
import * as Schemas from "../schemas.js"
const hatchedReact = hatchifyReact(createJsonapiClient("/api", Schemas))
const TodoDataGrid = hatchedReact.components.Todo.DataGrid // 👀
const App: React.FC = () => {
return (
<ThemeProvider theme={createTheme()}>
<HatchifyProvider>
<TodoDataGrid>
{/* 👀 */}
<TodoDataGrid.Empty>
{/* 👀 */}
<strong>There are no todos. Time to take a break!</strong>
</TodoDataGrid.Empty>
</TodoDataGrid>
</HatchifyProvider>
</ThemeProvider>
)
}
export default App
Your app should now look like this:
Notice how the content we've placed inside TodoDataGrid.Empty
now displays in our empty list.
- Because the
DataGrid.Empty
component is meant to be used only within aDataGrid
, you can access it by either appending.Empty
to theDataGrid
.
const TodoEmpty = hatchedReact.components.Todo.DataGrid.Empty
Or you can use your existing DataGrid
component and access Empty
as a property of it:
const TodoDataGrid = hatchedReact.components.Todo.DataGrid
return <TodoDataGrid.Empty />
-
The
<TodoDataGrid.Empty>
component specifies the content that should be displayed when the list is empty. To use it, simply put the empty list content within<TodoDataGrid.Empty>
and put<TodoDataGrid.Empty>
within the<TodoDataGrid>
component:<TodoDataGrid> <TodoDataGrid.Empty> <strong>There are no todos. Time to take a break!</strong> </TodoDataGrid.Empty> </TodoDataGrid>
And that's all there is to it! Next, lets explore DataGrid.Column
, which offers a lot more customization.
✏️ Before you proceed, post some seed data to your database. You can use the snippet from the Seeding Data section in the Hatchify getting started guide to do this.
DataGrid.Column
is a powerful compound component that allows you to make fine-grained customizations to your list. It can be used to make selective changes and additions, or it can be used to override your list entirely.
The behaviour of your DataGrid.Column
customizations is determined by the field
prop, as well as the overwrite
prop on the parent DataGrid
component. Depending on how you use the field
prop, a DataGrid.Column
is considered either "Extra" or "Custom". Let's start by looking at the difference between the two.
✏️ Update /src/App.tsx
to the following:
// hatchify-app/frontend/App.tsx
import { useState } from "react"
import { hatchifyReact, HatchifyProvider, createJsonapiClient } from "@hatchifyjs/react"
import { createTheme, ThemeProvider } from "@mui/material"
import * as Schemas from "../schemas.js"
const hatchedReact = hatchifyReact(createJsonapiClient("/api", Schemas))
const TodoDataGrid = hatchedReact.components.Todo.DataGrid
const App: React.FC = () => {
return (
<ThemeProvider theme={createTheme()}>
<HatchifyProvider>
<TodoDataGrid>
<TodoDataGrid.Empty>
<strong>There are no todos. Time to take a break!</strong>
</TodoDataGrid.Empty>
{/* 👀 */}
<TodoDataGrid.Column
field="name"
label="ToDo"
renderDataValue={({ value }) => {
return <strong>{value}</strong>
}}
renderHeaderValue={({ column: { label } }) => {
return <strong>{label} Items</strong>
}}
sortable={true}
/>
</TodoDataGrid>
</HatchifyProvider>
</ThemeProvider>
)
}
export default App
Your app should now look like this:
Notice how the column for the name
field has been replaced with our Custom column.
- Just like
Empty
, we can accessColumn
by appending.Column
to theDataGrid
component.
const TodoColumn = hatchedReact.components.Todo.DataGrid.Column
Or we can use our existing DataGrid
component and access Column
as a property of it:
const TodoDataGrid = hatchedReact.components.Todo.DataGrid
return <TodoDataGrid.Column />
-
By setting the
field
property on ourDataGrid.Column
toname
, we're establishing ourDataGrid.Column
as being a Custom column. What this does is tell Hatchify to apply our customizations to the column that corresponds to thename
field on our record.<TodoDataGrid.Column field="name" //...Remaining props... />
-
The
label
prop allows you to overwrite the column's default header with a custom string.<TodoDataGrid.Column label="ToDo" //...Remaining props... />
-
The
renderDataValue
prop allows you to pass in a callback that returns JSX. That JSX will fully overwrite the contents of each data cell.<TodoDataGrid.Column renderDataValue={({ value }) => { return <strong>{value}</strong> }} //...Remaining props... />
The callback accepts one argument: an object with a
value
property and arecord
property.value
is equal to the value that would have otherwise been rendered in each data cell.record
is equal to the full record represented by a given data cell's row.-
Alternatively, you could use the
DataValueComponent
prop instead ofrenderDataValue
. It does the same thing, but instead of accepting a callback it accepts a React component that optionally accepts arecord
prop and avalue
prop. It would look something like this:// Define your component: const CustomComponent = ({ value }: { value: string }) => { return <strong>{value}</strong> }
// Replace `renderDataValue` with `DataValueComponent` and pass in your `CustomComponent`: <TodoDataGrid.Column DataValueComponent={CustomComponent} //...Remaining props... />
-
-
The
renderHeaderValue
prop works just likerenderDataValue
, only the JSX returned by the callback will fully overwrite the contents of the header cell.renderHeaderValue={({ column: { label } }) => { return <strong>{label} Items</strong> }}
This example is a little contrived because the
label
we're destructuring in our app code above will always be equal to thelabel
prop that we set on ourDataGrid.Column
, so we could have simply omitted thelabel
prop entirely and hard-coded the value in the return of our callback.Just like
renderDataValue
,renderHeaderValue
accepts a single object as its argument, but its shape is more complex. Let's look at it in detail:{ column: { sortable, // Boolean that reflects whether or not sorting has been applied to the column key, // Unique key for the column label, // The column's label }, meta, // Contains metadata, including sort request pending status sortBy, // The field that the list is sorted by. This will always equal the field that the column corresponds to, if applicable direction, // The direction that the list is currently sorted by setSort, // A function for updating the list's sort }
- Just like
renderDataValue
,renderHeaderValue
can be replaced withHeaderValueComponent
, which works similarly toDataValueComponent
.
- Just like
-
The
sortable
prop tells Hatchify whether or not to render the default column sorting UI in the column's header cell.<TodoDataGrid.Column sortable={true} //...Remaining props... />
It defaults to
true
as long as yourfield
prop specifies a valid field on your record, so in this case it's redundant to include it--we just did so for demonstration purposes.Note that if you set
sortable
totrue
on a column with an invalidfield
prop, the default column sorting UI will display but using it will send an invalid network request (because the request won't contain a valid field to sort by).
Note: You may have noticed that the column for the id
field is missing. Hatchify hides it by default, but you can change this behavior by updating your schema to id: uuid({ primary: true })
.
Okay! We just learned how to selectively modify an existing column--now let's try adding brand new columns to our list.
✏️ Update /src/App.tsx
to the following:
// hatchify-app/frontend/App.tsx
import { useState } from "react"
import { hatchifyReact, HatchifyProvider, createJsonapiClient } from "@hatchifyjs/react"
import { createTheme, ThemeProvider } from "@mui/material"
import * as Schemas from "../schemas.js"
const hatchedReact = hatchifyReact(createJsonapiClient("/api", Schemas))
const TodoDataGrid = hatchedReact.components.Todo.DataGrid
const App: React.FC = () => {
return (
<ThemeProvider theme={createTheme()}>
<HatchifyProvider>
<TodoDataGrid>
<TodoDataGrid.Empty>
<strong>There are no todos. Time to take a break!</strong>
</TodoDataGrid.Empty>
{/* 👀 */}
<TodoDataGrid.Column
label="Actions"
renderDataValue={({ record }) => {
return <button onClick={() => alert(`${record.id}`)}>View ID</button>
}}
/>
<TodoDataGrid.Column
field="name"
label="ToDo"
renderDataValue={({ value }) => {
return <strong>{value}</strong>
}}
renderHeaderValue={({ column: { label } }) => {
return <strong>{label} Items</strong>
}}
sortable={true}
/>
</TodoDataGrid>
</HatchifyProvider>
</ThemeProvider>
)
}
export default App
Your app should now look like this:
Notice the new "Actions" column at the end of our list.
-
Columns without a
field
prop are treated as Extra columns and will always appear at the end of your list, even if you include them before Custom columns. In the below example, the "Actions" column will appear at the end of our table, even though it's defined before the "name" column:<TodoDataGrid.Column label="Actions" //...Remaining props... /> <TodoDataGrid.Column field="name" //...Remaining props... />
-
In our app code above, we removed the
field
prop because Extra columns represent brand new columns that inherently don't map directly to a field in our list. Despite this, we still have access to the entire row'srecord
object viarenderDataCell
, so we can still access any fields from our record that we need. -
If you want your column to appear at the beginning of your list, you can set the
prepend
prop to true.
With the two types of columns we just covered - Extra and Custom - you'll notice that when we made changes to our list, Hatchify continued to render all the other columns that we didn't modify as it normally would. So now let's take a look at how we can fully override Hatchify's column-rendering behavior.
✏️ Update /src/App.tsx
to the following:
// hatchify-app/frontend/App.tsx
import { useState } from "react"
import { hatchifyReact, HatchifyProvider, createJsonapiClient } from "@hatchifyjs/react"
import { createTheme, ThemeProvider } from "@mui/material"
import * as Schemas from "../schemas.js"
const hatchedReact = hatchifyReact(createJsonapiClient("/api", Schemas))
const TodoDataGrid = hatchedReact.components.Todo.DataGrid
const App: React.FC = () => {
return (
<ThemeProvider theme={createTheme()}>
<HatchifyProvider>
<TodoDataGrid
{/* 👀 */}
overwrite
>
<TodoDataGrid.Empty>
<strong>There are no todos. Time to take a break!</strong>
</TodoDataGrid.Empty>
{/* 👀 */}
<TodoDataGrid.Column
label="Override column"
renderDataValue={({ record }) => {
return <strong>{record.name}</strong>
}}
renderHeaderValue={({ column: { label } }) => {
return <strong>{label}</strong>
}}
/>
<TodoDataGrid.Column
prepend
label="Actions"
renderDataValue={({ record }) => {
return <button onClick={() => alert(`${record.id}`)}>View ID</button>
}}
/>
</TodoDataGrid>
</HatchifyProvider>
</ThemeProvider>
)
}
export default App
Your app should now look like this:
Notice that most of our columns are no longer rendering. Don't worry--this is by design:
- As soon as you add the
ovewrite
prop to theDataGrid
component, Hatchify will no longer render any columns that you don't specify. This means that you'll need to add aDataGrid.Column
component for each field that you want to render. - An example use case for this feature is a list that needs to be heavily customized; you might need all the fields on your records in order to populate your list, but each column requires computed values or needs to be in a custom order, etc.
- When in
overwrite
mode, the order will be determined by the order of yourDataGrid.Column
components, so if you want your columns to appear in a specific order, you'll need to specify them in that order. Theprepend
prop, or absence of it, has no effect when inoverwrite
mode.