admin管理员组

文章数量:1122846

I've set up a wrapper component for the Shopify Resource Picker.

import { useAppBridge } from '@shopify/app-bridge-react';
import { Button, type ButtonProps } from '@shopify/polaris';
import { useCallback } from 'react';

export type ResourcePickerSelectPayload = NonNullable<
  Awaited<ReturnType<typeof shopify.resourcePicker>>
>;

interface ProductResourcePickerProps {
  onSelect: (resources: ResourcePickerSelectPayload) => void;
  options?: Parameters<typeof shopify.resourcePicker>[0];
  buttonLabel?: string;
  buttonProps?: Omit<ButtonProps, 'onClick'>;
}

export function ResourcePicker({
  onSelect,
  options = { type: 'product' },
  buttonLabel = 'Select products',
  buttonProps = {},
}: ProductResourcePickerProps) {
  const shopify = useAppBridge();

  const handleOpenPicker = useCallback(async () => {
    const selected = await shopify.resourcePicker(options);

    if (selected) {
      onSelect(selected);
    }
  }, [onSelect, options, shopify]);

  return (
    <Button onClick={handleOpenPicker} {...buttonProps}>
      {buttonLabel}
    </Button>
  );
}

The return value of shopify.ResourcePicker is as the type:

type ResourcePickerApi = (options: ResourcePickerOptions) => Promise<SelectPayload<ResourcePickerOptions['type']> | undefined>;

which is causing my ResourcePickerSelectPayload to be:

type ResourcePickerSelectPayload = ResourceSelection<"product" | "variant" | "collection">[] & {
    selection: ResourceSelection<"product" | "variant" | "collection">[];
}

What I am struggling with is how to have the value from options.type (product, variant or collection) be passed through so that my component returns the narrowed type. i.e. if options.type === 'product' then selected would be the ResourceSelection<'product'>. The ResourceSelection type is not exported, so I don't know a way to access it besides what I've got. I think part of the issue is that my ResourcePickerSelectPayload is not a generic, but ResourceSelection is.

I tried to pass the value of options.type into my function and do some conditional returns but it didn't work.

I've set up a wrapper component for the Shopify Resource Picker.

import { useAppBridge } from '@shopify/app-bridge-react';
import { Button, type ButtonProps } from '@shopify/polaris';
import { useCallback } from 'react';

export type ResourcePickerSelectPayload = NonNullable<
  Awaited<ReturnType<typeof shopify.resourcePicker>>
>;

interface ProductResourcePickerProps {
  onSelect: (resources: ResourcePickerSelectPayload) => void;
  options?: Parameters<typeof shopify.resourcePicker>[0];
  buttonLabel?: string;
  buttonProps?: Omit<ButtonProps, 'onClick'>;
}

export function ResourcePicker({
  onSelect,
  options = { type: 'product' },
  buttonLabel = 'Select products',
  buttonProps = {},
}: ProductResourcePickerProps) {
  const shopify = useAppBridge();

  const handleOpenPicker = useCallback(async () => {
    const selected = await shopify.resourcePicker(options);

    if (selected) {
      onSelect(selected);
    }
  }, [onSelect, options, shopify]);

  return (
    <Button onClick={handleOpenPicker} {...buttonProps}>
      {buttonLabel}
    </Button>
  );
}

The return value of shopify.ResourcePicker is as the type:

type ResourcePickerApi = (options: ResourcePickerOptions) => Promise<SelectPayload<ResourcePickerOptions['type']> | undefined>;

which is causing my ResourcePickerSelectPayload to be:

type ResourcePickerSelectPayload = ResourceSelection<"product" | "variant" | "collection">[] & {
    selection: ResourceSelection<"product" | "variant" | "collection">[];
}

What I am struggling with is how to have the value from options.type (product, variant or collection) be passed through so that my component returns the narrowed type. i.e. if options.type === 'product' then selected would be the ResourceSelection<'product'>. The ResourceSelection type is not exported, so I don't know a way to access it besides what I've got. I think part of the issue is that my ResourcePickerSelectPayload is not a generic, but ResourceSelection is.

I tried to pass the value of options.type into my function and do some conditional returns but it didn't work.

Share Improve this question asked Nov 22, 2024 at 4:22 NicONicO 751 silver badge2 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

Generics come in very handy here. They basically allow you to dynamically infer and enforce types based on the input.

With generics, we could rewrite the code to look like this;

interface ProductResourcePickerProps<
  T extends "product" | "variant" | "collection"
> {
  onSelect: (resources: ResourceSelection<T>[]) => void;
  options?: { type: T } & Omit<ResourcePickerOptions, "type">;
  buttonLabel?: string;
  buttonProps?: Omit<ButtonProps, "onClick">;
}

export function ResourcePicker<T extends "product" | "variant" | "collection">({
  onSelect,
  options = { type: "product" } as { type: T },
  buttonLabel = "Select resources",
  buttonProps = {},
}: ProductResourcePickerProps<T>) {
  const shopify = useAppBridge();

  const handleOpenPicker = useCallback(async () => {
    const selected = await shopify.resourcePicker(options);

    if (selected?.selection) {
      onSelect(selected.selection);
    }
  }, [onSelect, options, shopify]);

  return (
    <Button onClick={handleOpenPicker} {...buttonProps}>
      {buttonLabel}
    </Button>
  );
}

So once you call ResourcePicker, onSelect already knows what came in. Something like this;

 <ResourcePicker
  onSelect={(selected) => {
    // TypeScript shows ResourceSelection<'product'>[] as the type already
    console.log(selected);
  }}
  options={{ type: 'product' }}
  buttonLabel="Select products"
/>

For what it's worth, I checked the resource picker documentation and I think there's more to do in this wrapper, but focusing on the context of the code in the OP, this explains the use of generics to solve your problem.

本文标签: