Skip to content

Commit

Permalink
metafieldParser() (#45)
Browse files Browse the repository at this point in the history
* Save progress on a new metafield parser

* Fix type issue, and get some generics passed in

* Fix tests and add additional TS docs to the types

* handle no 'type' field, and prep for list metafields

* Save progress on metafield parser and tests

* Finished the tests and docs

* Deprecate parseMetafield; add changelog; export functions

* fix bad variable name

* Update packages/react/src/metafield-parser.test.ts

Co-authored-by: Daniel Rios <ieldanr@gmail.com>

* Clarify wording around naming and deprecation

* remove todo

Co-authored-by: Daniel Rios <ieldanr@gmail.com>
  • Loading branch information
frehner and lordofthecactus authored Oct 27, 2022
1 parent 3eab955 commit 1ccbd1c
Show file tree
Hide file tree
Showing 8 changed files with 1,056 additions and 37 deletions.
55 changes: 55 additions & 0 deletions .changeset/nine-garlics-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
'@shopify/hydrogen-react': patch
---

Introducing the new `metafieldParser()` function and `ParsedMetafield` type.

## `metafieldParser()`

`metafieldParser()` is a temporary name; it will be renamed to `parseMetafield()` in a future release.

The `metafieldParser()` function is an improvement and enhancement upon the existing `parseMetafield()` and `parseMetafieldValue()` functions. `metafieldParser()` now supports all Metafield types as outlined in the [Storefront API](https://shopify.dev/apps/metafields/types) documentation, including the list types!

The parsed value can be found on the newly-added `parsedValue` property of the returned object from `metafieldParser()`. For example:

```js
const parsed = metafieldParser(metafield);

console.log(parsed.parsedValue);
```

`parseMetafieldValue()` has been marked as deprecated and will be removed in a future version of Hydrogen-UI.

## The `ParsedMetafield` type

For TypeScript developers, we also introduce the new `ParsedMetafield` type to help improve your experience. The `ParsedMetafield` type is an object in which the keys map to the type that will be returned from `metafieldParser()`. For example:

```ts
ParsedMetafield['boolean'];
// or
ParsedMetafield['list.collection'];
```

When used in conjunction with `metafieldParser()`, it will help TypeScript to understand what the returned object's `parsedValue` type is:

```ts
const parsed = metafieldParser<ParsedMetafield['boolean']>(booleanMetafield)

// type of `parsedValue` is `boolean | null`
if(parsed.parsedValue) {
...
}
```

or

```ts
const parsed = metafieldParser<ParsedMetafield['list.collection']>(
listCollectionMetafield
);

// type of `parsedValue` is `Array<Collection> | null`
parsed.parsedValue?.map((collection) => {
console.log(collection?.name);
});
```
1 change: 1 addition & 0 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"happy-dom": "7.5.10",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"ts-expect": "^1.3.0",
"typescript": "^4.8.4",
"vite": "^3.1.8",
"vitest": "^0.24.3"
Expand Down
50 changes: 15 additions & 35 deletions packages/react/src/Metafield.test.helpers.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import {faker} from '@faker-js/faker';
import type {Metafield as MetafieldType} from './storefront-api-types.js';
import type {PartialDeep} from 'type-fest';
import {
type MetafieldTypeTypes,
allMetafieldTypesArray,
} from './metafield-parser.js';

export function getRawMetafield(
metafield: PartialDeep<MetafieldType, {recurseIntoArrays: true}> & {
type?: MetafieldTypeOptions;
type?: MetafieldTypeTypes;
} = {}
): PartialDeep<MetafieldType, {recurseIntoArrays: true}> {
const type: MetafieldTypeOptions =
): PartialDeep<MetafieldType, {recurseIntoArrays: true}> & {
type: MetafieldTypeTypes;
} {
const type: MetafieldTypeTypes =
metafield.type == null
? faker.helpers.arrayElement(METAFIELD_TYPES)
? faker.helpers.arrayElement(allMetafieldTypesArray)
: metafield.type;

return {
Expand All @@ -23,20 +29,16 @@ export function getRawMetafield(
updatedAt: metafield.updatedAt ?? faker.date.recent().toString(),
value: metafield.value ?? getMetafieldValue(type),
reference: metafield.reference,
references: metafield.references,
};
}

export function getMetafieldValue(type: MetafieldTypeOptions) {
export function getMetafieldValue(type: MetafieldTypeTypes) {
switch (type) {
case 'single_line_text_field':
return faker.random.words();
case 'multi_line_text_field':
return `${faker.random.words()}\n${faker.random.words()}\n${faker.random.words()}`;
case 'page_reference':
case 'product_reference':
case 'variant_reference':
case 'file_reference':
return faker.random.words();
case 'number_integer':
return faker.datatype.number().toString();
case 'number_decimal':
Expand Down Expand Up @@ -89,30 +91,8 @@ export function getMetafieldValue(type: MetafieldTypeOptions) {
value: faker.datatype.float({min, max, precision: 0.0001}),
});
}
default:
return JSON.stringify(faker.datatype.json());
default: {
return faker.random.words();
}
}
}

export const METAFIELD_TYPES = [
'single_line_text_field',
'multi_line_text_field',
'page_reference',
'product_reference',
'variant_reference',
'file_reference',
'number_integer',
'number_decimal',
'date',
'date_time',
'url',
'json',
'boolean',
'color',
'weight',
'volume',
'dimension',
'rating',
] as const;

type MetafieldTypeOptions = typeof METAFIELD_TYPES[number];
17 changes: 15 additions & 2 deletions packages/react/src/Metafield.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,19 @@ export function Metafield<ComponentGeneric extends ElementType>(
* The `parseMetafield` utility transforms a [Metafield](https://shopify.dev/api/storefront/reference/common-objects/Metafield)
* into a new object whose `values` have been parsed according to the metafield `type`.
* If the metafield is `null`, then it returns `null` back.
*
* Note that `parseMetafield()` will have a breaking change in a future version; it will change to behave like `metafieldParser()`.
*/
export function parseMetafield(
/** A [Metafield](https://shopify.dev/api/storefront/reference/common-objects/Metafield) or null */
metafield: PartialDeep<MetafieldType, {recurseIntoArrays: true}> | null
): PartialDeep<ParsedMetafield, {recurseIntoArrays: true}> | null {
if (__HYDROGEN_DEV__) {
console.info(
`'parseMetafield()' will have a breaking change in a future version; its behavior will match that of 'metafieldParser()'`
);
}

if (!metafield) {
if (__HYDROGEN_DEV__) {
console.warn(
Expand All @@ -220,10 +228,15 @@ export function parseMetafield(

/**
* The `parseMetafieldValue` function parses a [Metafield](https://shopify.dev/api/storefront/reference/common-objects/metafield)'s `value` from a string into a sensible type corresponding to the [Metafield](https://shopify.dev/api/storefront/reference/common-objects/metafield)'s `type`.
* @deprecated `parseMetafieldValue()` is unsupported and will be removed in a future version.
*/
export function parseMetafieldValue(
metafield: PartialDeep<MetafieldType, {recurseIntoArrays: true}> | null
): ParsedMetafield['value'] {
if (__HYDROGEN_DEV__) {
console.info(`'parseMetafieldValue()' will be removed in a future version`);
}

if (!metafield) {
return null;
}
Expand Down Expand Up @@ -304,7 +317,7 @@ export function getMeasurementAsString(
locale = 'en-us',
options: Intl.NumberFormatOptions = {}
) {
let measure: {value: number; unit: string} = {
let measure: Measurement = {
value: measurement.value,
unit: UNIT_MAPPING[measurement.unit],
};
Expand All @@ -320,7 +333,7 @@ export function getMeasurementAsString(
}).format(measure.value);
}

function convertToSupportedUnit(value: number, unit: string) {
function convertToSupportedUnit(value: number, unit: string): Measurement {
switch (unit) {
case 'cl':
return {
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export {ExternalVideo} from './ExternalVideo.js';
export {flattenConnection} from './flatten-connection.js';
export {Image} from './Image.js';
export {MediaFile} from './MediaFile.js';
export {metafieldParser, type ParsedMetafields} from './metafield-parser.js';
export {Metafield, parseMetafield, parseMetafieldValue} from './Metafield.js';
export {ModelViewer} from './ModelViewer.js';
export {Money} from './Money.js';
Expand Down
Loading

0 comments on commit 1ccbd1c

Please sign in to comment.