Unleash the Power of Repositories - Introducing My CRUD Table React Component
What and why?
I am building a DoJo (Martial Arts School) manager as my participation in the Hashnode hackathon. The leader of a dojo should be able to manage the students
, their contracts
, payments
and classes
.
Because I have several different models, which can be CREATED
, READ
, UPDATED
and DELETED
, I took the approach of building a CRUD table, that just needs some information about how to access the model.
Models used for this project (and in this post):
student
master
What do I want to build?
I want to get a list of all existing documents for a given model, displayed in a table
I want the table to do the heavy lifting (loading, removing and creating documents via the passed-in repository)
Prerequisites
Repository pattern for your data access
This is only worth doing if you have a lot of CRUD Models
Implementation
1 - Get your repository in place
The following snippet shows the API of my InMemoryRepository
. If you are curious about the actual implementation, find it here on GitHub.
export const BuildInMemoryRepositoryFor = (sampleData = [], filterHandler = (items, filter) => { return items; }) => {
const items = sampleData;
let cache = [];
return {
create: (data) => {/*logic...*/},
delete: (id) => {/*logic...*/},
update: (id, updatePayload) => {/*logic...*/},
list: (filter = undefined) => {/*logic...*/},
get: (id) => {/*logic...*/},
count: () => {/*logic...*/},
}
}
This comes in handy. when I define the InMemoryRepository
for my teacher
and my student
model.
// repositories.js
export const InMemoryMasterRepository = BuildInMemoryRepositoryFor([]);
export const InMemoryStudentRepository = BuildInMemoryRepositoryFor([]);
2 - Coding our CRUD table component
export function CrudTable({repository, headers}) {
const [documents, setDocuments] = useState([]);
useEffect(() => {
const fetch = async () => {
setDocuments((await repository.list())); // access our repository
};
fetch();
}, []);
const delete = async (document) => {
await repository.delete(document);
}
return <table>
<thead>
{headers.map((header) => {
return <th>{header}</th>;
})}
<th>Actions</th>
</thead>
<tbody>
{documents.map((document) => {
return <tr>
{headers.map((header) => {
return <td>{document[header]}</td>
})}
<td><p onClick={() => delete(document)}>Delete</p></td>
</tr>
}
</tbody>
</table>
}
3 - Integrating it
Inside your page, you can now integrate the Crud table.
import {InMemoryMasterRepository} from './repositories.js';
export function DashboardPage() {
return <CrudTable repository={InMemoryMasterRepository} headers={['first_name', 'last_name']}/>
}
4 - Wait; There is more to CRUD, isn't it?
Yes! In addition to READ
and DELETE
operations, my CRUD table also includes CREATE
and UPDATE
methods. To facilitate this, I've developed a versatile ResourceForm
component. It allows you to define the fields to be displayed in the form and specify validation rules.
For a comprehensive implementation of the CRUD table, including the ResourceForm component, you can explore the complete code in my GitHub repository.
Limitations
Nothing is as great as it seems at first. The current approach is not able to populate any data. You would need to do this on the repository end of your data.
For example: if I want to map a student
to a master
. I can do this via the ID. This, however, will just display the id in the table, which is kind of nonsense.
Conclusion
You need to balance yourself and your project if you are going to need this. If you have a lot of models that should be viewable and editable in a similar way, then this might be a great approach for you.