Design - General design principles
This covers some high level design principles that don’t seem to fit anywhere else.
When possible, Sort/Filter in the Backend rather than the Frontend
If you have a page where you are presenting information that always needs to be presented to the user initially in a certain order, you have two choices:
- Sort/filter the data in the backend at the API endpoint
- Sort/filter the data on the frontend
It will almost always be a better choice to sort the data in the backend.
- It’s typically easier to code and test.
- It’s typically faster, performance wise
You have two choices in the backend:
Choice 1: Let the database do the sorting/filtering
This is almost always the better option when it’s possible: For example, consider this example PR from proj-dining
Moving Filtering to backend:
Before:
Iterable<Review> reviews = reviewRepository.findAll();
After:
Iterable<Review> reviews = reviewRepository.findByItemAndStatus(item, ModerationStatus.APPROVED);
This eliminates the need for frontend filters such as:
const filteredReviews =
reviews?.filter((review) => review.status === "AWAITING_REVIEW") || [];
or
const filteredReviews =
reviews?.filter((review) => review.item.id === Number(itemid)) || [];
For sorting:
Consider this example from proj-courses:
Before:
Iterable<Job> jobs = jobsRepository.findAll();
After:
Iterable<Job> jobs = jobsRepository.findAllByOrderByIdDesc();
Now there’s no need to apply an additional sort in the frontend.
Choice 2: Do it with Java collection utilities
Sorting of Java Collections is described in this article on the CS156 website.
In this case, we had to define a new collection by wrapping the result before sorting (since the result returned by convertedSectionCollection.findByQuarterRangeAndBuildingCode
was an immutable collection):
Before:
List<ConvertedSection> courseResults =
convertedSectionCollection.findByQuarterRangeAndBuildingCode(
startQtr, endQtr, buildingCode);
After:
List<ConvertedSection> courseResults = new java.util.ArrayList<>(
convertedSectionCollection.findByQuarterRangeAndBuildingCode(
startQtr, endQtr, buildingCode));
courseResults.sort(new ConvertedSection.ConvertedSectionSortDescendingByQuarterComparator());
The comparator is defined here as an inner class of ConvertedSection
:
public static class ConvertedSectionSortDescendingByQuarterComparator implements java.util.Comparator<ConvertedSection> {
@Override
public int compare(ConvertedSection o1, ConvertedSection o2) {
return o2.getCourseInfo().getQuarter().compareTo(o1.getCourseInfo().getQuarter());
}
}
Considerations
Consider whether the backend endpoint used by only one page in the app (which means you can sort any way you please for that page), or multiple pages in the app? If it’s used by multiple pages, consider defining a new endpoint, or adding a new optional parameter to endpoint to specify the sort or filtering criteria.
If you must do it in the frontend, use React-Table
If you have to do it in the frontend, you can do it with React-Table.
Here’s an example of sorting a table descending by it’s id from a version of JobsTable (before we refactored it to sort on the backend):
const sortByIdDescending = {
sorting: [
{
id: "id",
desc: true, // sort by name in descending order by default
},
],
};
return (
<OurTable
data={jobs}
columns={columns}
testid={testid}
initialState={sortByIdDescending}
/>
);
But then you have to cover it in tests: for example:
// Check that rows are sorted by id in descending order
const rows = screen.getAllByRole("row");
expect(rows).toHaveLength(7); // 6 jobs + 1 header row
expect(rows[1]).toHaveTextContent("6");
expect(rows[2]).toHaveTextContent("5");
expect(rows[3]).toHaveTextContent("4");
expect(rows[4]).toHaveTextContent("3");
expect(rows[5]).toHaveTextContent("2");
expect(rows[6]).toHaveTextContent("1");