Skip to content

Commit

Permalink
docs: update React grid data provider example (#4133)
Browse files Browse the repository at this point in the history
* docs: update React grid data provider example

* fix mock not being used

* make it work without a JPA repository

* use new grid data provider hook

* remove count implementation
  • Loading branch information
sissbruecker authored Feb 27, 2025
1 parent 2f54cae commit 32465c1
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 72 deletions.
8 changes: 8 additions & 0 deletions articles/components/grid/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,14 @@ ifdef::react[]
----
include::{root}/frontend/demo/component/grid/react/grid-data-provider.tsx[render,tags=snippet,indent=0,group=React]
----
[source,java]
----
include::{root}/src/main/java/com/vaadin/demo/component/grid/GridPersonService.java[render,tags=snippet,indent=0,group=React]
----
[source,java]
----
include::{root}/src/main/java/com/vaadin/demo/component/grid/GridPersonRepository.java[render,tags=snippet,indent=0,group=React]
----
endif::[]
--

Expand Down
81 changes: 10 additions & 71 deletions frontend/demo/component/grid/react/grid-data-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,88 +1,27 @@
import '@vaadin/icons';
import { reactExample } from 'Frontend/demo/react-example'; // hidden-source-line
import React, { useMemo } from 'react';
import React from 'react';
import { useSignals } from '@preact/signals-react/runtime'; // hidden-source-line
import { useGridDataProvider } from '@vaadin/hilla-react-crud';
import { useSignal } from '@vaadin/hilla-react-signals';
import {
Grid,
type GridDataProviderCallback,
type GridDataProviderParams,
type GridSorterDefinition,
type GridSorterDirection,
} from '@vaadin/react-components/Grid.js';
import { Grid } from '@vaadin/react-components/Grid.js';
import { GridSortColumn } from '@vaadin/react-components/GridSortColumn.js';
import { Icon } from '@vaadin/react-components/Icon.js';
import { TextField } from '@vaadin/react-components/TextField.js';
import { VerticalLayout } from '@vaadin/react-components/VerticalLayout.js';
import { getPeople } from 'Frontend/demo/domain/DataService';
import type Person from 'Frontend/generated/com/vaadin/demo/domain/Person';

function matchesTerm(value: string, searchTerm: string) {
return value.toLowerCase().includes(searchTerm.toLowerCase());
}

function compare(a: string, b: string, direction: GridSorterDirection) {
return direction === 'asc' ? a.localeCompare(b) : b.localeCompare(a);
}
import { GridPersonService } from 'Frontend/generated/endpoints';

// tag::snippet[]
async function fetchPeople(params: {
page: number;
pageSize: number;
searchTerm: string;
sortOrders: GridSorterDefinition[];
}) {
const { page, pageSize, searchTerm, sortOrders } = params;
const { people } = await getPeople();
let result = people.map((person) => ({
...person,
fullName: `${person.firstName} ${person.lastName}`,
}));

// Filtering
if (searchTerm) {
result = result.filter(
(p) => matchesTerm(p.fullName, searchTerm) || matchesTerm(p.profession, searchTerm)
);
}

// Sorting
const sortBy = Object.fromEntries(sortOrders.map(({ path, direction }) => [path, direction]));
if (sortBy.fullName) {
result = result.sort((p1, p2) => compare(p1.fullName, p2.fullName, sortBy.fullName));
} else if (sortBy.profession) {
result = result.sort((p1, p2) => compare(p1.profession, p2.profession, sortBy.profession));
}

// Pagination
const count = result.length;
const offset = page * pageSize;
result = result.slice(offset, offset + pageSize);

return { people: result, count };
}

function Example() {
useSignals(); // hidden-source-line
const searchTerm = useSignal('');

const dataProvider = useMemo(
() =>
async (
params: GridDataProviderParams<Person>,
callback: GridDataProviderCallback<Person>
) => {
const { page, pageSize, sortOrders } = params;

const { people, count } = await fetchPeople({
page,
pageSize,
sortOrders,
searchTerm: searchTerm.value,
});

callback(people, count);
},
// Create a data provider that calls a backend service with a
// Spring Data pageable and the search term
const dataProvider = useGridDataProvider(
async (pageable) => GridPersonService.list(pageable, searchTerm.value),
// Providing the search term as a dependency will automatically
// refresh the data provider when the search term changes
[searchTerm.value]
);

Expand Down
53 changes: 53 additions & 0 deletions frontend/demo/services/GridPersonService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { getPeople } from 'Frontend/demo/domain/DataService';
import { CrudMockService } from 'Frontend/demo/services/CrudService';
import type Person from 'Frontend/generated/com/vaadin/demo/domain/Person';
import type OrFilter from 'Frontend/generated/com/vaadin/hilla/crud/filter/OrFilter';
import Matcher from 'Frontend/generated/com/vaadin/hilla/crud/filter/PropertyStringFilter/Matcher';
import type Pageable from 'Frontend/generated/com/vaadin/hilla/mappedtypes/Pageable';

interface PersonWithFullName extends Person {
fullName: string;
}

class GridPersonService {
private mockService?: CrudMockService<PersonWithFullName>;

async list(pageable: Pageable, searchTerm: string): Promise<PersonWithFullName[]> {
await this.initMockService();

return this.mockService!.list(pageable, this.createFilter(searchTerm));
}

private createFilter(searchTerm: string): OrFilter {
return {
'@type': 'or',
children: [
{
'@type': 'propertyString',
propertyId: 'fullName',
filterValue: searchTerm,
matcher: Matcher.CONTAINS,
},
{
'@type': 'propertyString',
propertyId: 'profession',
filterValue: searchTerm,
matcher: Matcher.CONTAINS,
},
],
};
}

private async initMockService() {
if (this.mockService) {
return;
}
const data = (await getPeople()).people.map((person) => ({
...person,
fullName: `${person.firstName} ${person.lastName}`,
}));
this.mockService = new CrudMockService(data);
}
}

export default new GridPersonService();
3 changes: 2 additions & 1 deletion frontend/demo/services/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// During the build, the `Frontend/generated/endpoints` import is replaced with this module
import DashboardService from 'Frontend/demo/services/DashboardService';
import EmployeeService from 'Frontend/demo/services/EmployeeService';
import GridPersonService from 'Frontend/demo/services/GridPersonService';
import ProductService from 'Frontend/demo/services/ProductService';
export * from 'Frontend/generated/endpoints.js';

export { DashboardService, EmployeeService, ProductService };
export { DashboardService, EmployeeService, ProductService, GridPersonService };
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.vaadin.demo.component.grid;

import com.vaadin.demo.domain.Person;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;

import java.util.List;

// hidden-source-line - We don't want to register an actual JPA repository here
// hidden-source-line - So we put the source code to show in a comment and declare a dummy interface that is hidden in the docs
// tag::snippet[]
/* // hidden-source-line
public interface GridPersonRepository extends JpaRepository<Person, Long> {
List<Person> findByFullNameContainingIgnoreCaseOrProfessionContainingIgnoreCase(
String fullName, String profession, Pageable pageable);
}
*/ // hidden-source-line
// end::snippet[]

@Component // hidden-source-line
public class GridPersonRepository { // hidden-source-line
List<Person> findByFullNameContainingIgnoreCaseOrProfessionContainingIgnoreCase(String fullName, String profession, Pageable pageable) { // hidden-source-line
return List.of(); // hidden-source-line
} // hidden-source-line
}// hidden-source-line
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.vaadin.demo.component.grid;

import com.vaadin.demo.domain.Person;
import com.vaadin.flow.server.auth.AnonymousAllowed;
import com.vaadin.hilla.BrowserCallable;
import org.jspecify.annotations.NonNull;
import org.springframework.data.domain.Pageable;

import java.util.List;

// tag::snippet[]
@BrowserCallable
@AnonymousAllowed
public class GridPersonService {
private final GridPersonRepository personRepository;

public GridPersonService(GridPersonRepository personRepository) {
this.personRepository = personRepository;
}

public @NonNull List<@NonNull Person> list(Pageable pageable, String filter) {
// Implement your data fetching logic here
// For this example, we're using a Spring Data repository
return personRepository.findByFullNameContainingIgnoreCaseOrProfessionContainingIgnoreCase(filter, filter, pageable);
}
}
// end::snippet[]

0 comments on commit 32465c1

Please sign in to comment.