Skip to content

Commit a8396a2

Browse files
committed
Add type-safe useValues hook
1 parent e4b5dc2 commit a8396a2

File tree

4 files changed

+86
-54
lines changed

4 files changed

+86
-54
lines changed

packages/app/README.md

Lines changed: 52 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -541,71 +541,73 @@ function MyApp() {
541541
}
542542
```
543543

544-
For convenience, the following hooks are available to access data through the
545-
data context: `useEntity`, `useDatasets`, `useDatasetValue`,
546-
`useDatasetsValues`, `usePrefetchValues`.
547-
548-
We also provide a large number of type guards and assertion functions to narrow
549-
down the kind/shape/type of HDF5 entities returned by `useEntity`, as well as a
550-
memoised hook called `useNdArray` to create ndarrays that can be passed to the
551-
visualization components (available in `@h5web/lib`).
544+
A common need is to find specific datasets in a file and retrieve their values.
545+
You can do so with hooks `useDatasets` and `useValues` as follows:
552546

553547
```tsx
554-
function MyApp() {
555-
const entity = useEntity('/nD_datasets/twoD'); // ProvidedEntity
556-
assertDataset(entity); // Dataset
557-
assertArrayShape(entity); // Dataset<ArrayShape>
558-
assertFloatType(entity); // Dataset<ArrayShape, FloatType>
548+
const DATASETS_DEFS = {
549+
twoD: { path: '/twoD', shape: ShapeClass.Array, type: DTypeClass.Float }
550+
title: { path: '/title', shape: ShapeClass.Scalar, type: DTypeClass.String }
551+
};
559552

560-
const value = useDatasetValue(entity); // number[] | TypedArray
561-
const dataArray = useNdArray(value, entity.shape);
562-
const domain = useDomain(dataArray);
553+
function MyApp() {
554+
const datasets = useDatasets(DATASETS_DEFS);
555+
const { twoD, title } = useValues(datasets); // `number[] | TypedArray` and `string` respectively
563556

564-
return (
565-
<HeatmapVis
566-
style={{ width: '100vw', height: '100vh' }}
567-
dataArray={dataArray}
568-
domain={domain}
569-
/>
570-
);
557+
// Or if you just need a specific slice from the `twoD` dataset:
558+
const { slice, title } = useValues({
559+
slice: { dataset: datasets.twoD, selection: '2,:' },
560+
title: dataset.title,
561+
})
571562
}
572563
```
573564

574-
The `useDatasets` hook is a declarative alternative to `useEntity` that can
575-
retrieve multiple datasets at once and doesn't require invoking assertion
576-
functions afterwards:
565+
We also provide two simpler hooks, `useEntity` and `useValue`, as well as a
566+
large number of type guards and assertion functions to narrow down the
567+
kind/shape/type of HDF5 entities returned by `useEntity`.
577568

578-
```ts
579-
// `twoD` => `Dataset<ArrayShape, FloatType>`
580-
// `title` => `Dataset<ScalarShape, StringType>`
581-
const { twoD, title } = useDatasets({
582-
twoD: { path: '/nD_datasets/twoD', shape: ShapeClass.Array, type: DTypeClass.Float }
583-
title: { path: '/path/to/title', shape: ShapeClass.Scalar, type: DTypeClass.String }
584-
});
569+
```tsx
570+
const entity = useEntity('/nD_datasets/twoD'); // ProvidedEntity
571+
assertDataset(entity); // Dataset
572+
assertArrayShape(entity); // Dataset<ArrayShape>
573+
assertFloatType(entity); // Dataset<ArrayShape, FloatType>
574+
575+
const value = useValue(entity); // number[] | TypedArray
576+
577+
// Or a specific slice:
578+
const slice = useValue(entity, '2,:');
585579
```
586580

587-
When accessing the values of multiple datasets with multiple consecutive calls
588-
to `useDatasetValue` (and/or `useDatasetsValues`), invoke `usePrefetchValues`
589-
first to ensure that the values are requested in parallel rather than
590-
sequentially:
581+
Once you have a raw value array, you can use the memoised hook `useNdArray` to
582+
wrap it in an ndarray, and then pass it down to a visualization component from
583+
`@h5web/lib`:
591584

592585
```tsx
593-
const axesDatasets = [abscissasDataset, ordinatesDataset];
594-
usePrefetchValues([valuesDataset, ...axesDatasets]);
595-
596-
const values = useDatasetValue(valuesDataset);
597-
const [abscissas, ordinates] = useDatasetsValues(axesDatasets);
586+
const value = useValue(entity); // number[] | TypedArray
587+
const dataArray = useNdArray(value, entity.shape); // NdArray<number[] | TypedArray>
588+
const domain = useDomain(dataArray); // [number, number]
589+
590+
return (
591+
<HeatmapVis
592+
style={{ width: '100vw', height: '100vh' }}
593+
dataArray={dataArray}
594+
domain={domain}
595+
/>
596+
);
598597
```
599598

600-
All three hooks accept a `selection` parameter to request specific slices from
601-
n-dimensional datasets:
599+
Every store comes with a `prefetch` method that works like `get` but doesn't
600+
trigger the `Suspense` boundary and doesn't return a value. If you work with a
601+
remote provider like H5Grove and need to access multiple entities/values at
602+
once, it's important to prefetch every entity/value first so the requests are
603+
done in parallel. `useDatasets` and `useValues` do this automatically, but not
604+
`useEntity` and `useValue`:
602605

603606
```tsx
604-
const selection = '0,:,:';
605-
usePrefetchValues([valuesDataset], selection); // prefetch the first 2D slice
606-
usePrefetchValues([abscissasDataset, ordinatesDataset]); // pretech in full (i.e. no selection)
607+
const { valuesStore } = useDataContext();
608+
valuesStore.prefetch(abscissasDataset);
609+
valuesStore.prefetch(ordinatesDataset);
607610

608-
const values = useDatasetValue(valuesDataset, selection);
609-
const abscissas = useDatasetValue(abscissasDataset);
610-
const ordinates = useDatasetValue(ordinatesDataset);
611+
const abscissas = useValue(abscissasDataset);
612+
const ordinates = useValue(ordinatesDataset);
611613
```

packages/app/src/hooks.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
} from '@h5web/shared/hdf5-models';
1717

1818
import { useDataContext } from './providers/DataProvider';
19+
import { type ValuesStoreParams } from './providers/models';
1920

2021
export function useEntity(path: string): ProvidedEntity {
2122
const { entitiesStore } = useDataContext();
@@ -77,6 +78,33 @@ export function useValue<D extends Dataset<ArrayShape | ScalarShape>>(
7778
return value;
7879
}
7980

81+
type ValueFromParams<
82+
T extends ValuesStoreParams['dataset'] | ValuesStoreParams,
83+
> = Value<T extends ValuesStoreParams ? T['dataset'] : T>;
84+
85+
export function useValues<
86+
R extends Record<string, ValuesStoreParams['dataset'] | ValuesStoreParams>,
87+
>(datasets: R): { [K in keyof R]: ValueFromParams<R[K]> } {
88+
const { valuesStore } = useDataContext();
89+
90+
const withSelections = Object.entries(datasets).map(([key, dataset]) => {
91+
return [
92+
key,
93+
'dataset' in dataset ? dataset : { dataset, selection: undefined },
94+
] as const;
95+
});
96+
97+
withSelections.forEach(([, { dataset, selection }]) => {
98+
valuesStore.prefetch({ dataset, selection });
99+
});
100+
101+
return Object.fromEntries(
102+
withSelections.map(([key, withSelection]) => {
103+
return [key, valuesStore.get(withSelection)];
104+
}),
105+
) as { [K in keyof R]: ValueFromParams<R[K]> };
106+
}
107+
80108
export function useValuesInCache(
81109
...datasets: (Dataset<ScalarShape | ArrayShape> | undefined)[]
82110
): (dimMapping: DimensionMapping) => boolean {

packages/app/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export type {
3838
} from './providers/models';
3939

4040
// Hooks
41-
export { useEntity, useDatasets, useValue } from './hooks';
41+
export { useEntity, useDatasets, useValue, useValues } from './hooks';
4242
export { useBaseArray as useNdArray } from './vis-packs/core/hooks';
4343

4444
// Models

packages/app/src/vis-packs/nexus/NxNoteFetcher.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
} from '@h5web/shared/hdf5-models';
66
import { type ReactNode } from 'react';
77

8-
import { useNxValues, usePrefetchNxValues } from './hooks';
8+
import { useValues } from '../../hooks';
99

1010
interface Props {
1111
dataDataset: Dataset<ScalarShape, StringType>;
@@ -16,8 +16,10 @@ interface Props {
1616
function NxNoteFetcher(props: Props) {
1717
const { dataDataset, typeDataset, render } = props;
1818

19-
usePrefetchNxValues([dataDataset, typeDataset]);
20-
const [value, mimeType] = useNxValues([dataDataset, typeDataset]);
19+
const { value, mimeType } = useValues({
20+
value: dataDataset,
21+
mimeType: typeDataset,
22+
});
2123

2224
return <>{render(value, mimeType)}</>;
2325
}

0 commit comments

Comments
 (0)