vholik / medusa-custom-attributes

Plugin that extends MedusaJs with custom attributes API
70 stars 17 forks source link

Issue with integration with default storefront || ReferenceError: useAttributes is not defined #8

Closed Sylvesterx1 closed 7 months ago

Sylvesterx1 commented 7 months ago

Follows the integration documentation with the Medusa core storefront. For refference: https://rigby.pl/en/blog/custom-attributes-in-medusa.js-part-2-ui

I created: @modules/attributes/components/product-filters-context

import React from "react";

interface ProductFiltersContextProps {
  attributes?: Record<string, string[]> | null;
  intAttributes?: Record<string, number[]> | null;
  setAttributes?: React.Dispatch<React.SetStateAction<Record<string, string[]> | null>>
  setIntAttributes?: React.Dispatch<
    React.SetStateAction<Record<string, number[]> | null | undefined>
  >;
}

const ProductFiltersContext = React.createContext<ProductFiltersContextProps>(
  {}
);

interface ProductFiltersProviderProps {
  initialValues: ProductFiltersContextProps;
  children: React.ReactNode;
}

export const ProductFiltersProvider = ({
  children,
  initialValues,
}: ProductFiltersProviderProps) => {
  const {
    attributes: initialAttributes,
    intAttributes: initialIntAttributes, // Range attributes
  } = initialValues;

  const [intAttributes, setIntAttributes] =
    React.useState<Record<string, number[]>>(initialIntAttributes ?? {});

  const [attributes, setAttributes] = React.useState<Record<
    string,
    string[]
  > | null>(initialAttributes ?? {});

  return (
    <ProductFiltersContext.Provider
      value={{
        attributes,
        setAttributes,
        intAttributes,
        setIntAttributes,
      }}
    >
      {children}
    </ProductFiltersContext.Provider>
  );
};

export const useProductFilters = () => {
  const context = React.useContext(ProductFiltersContext);

  return context;
};

@modules/attributes/components/attribute-filters

import React from "react";
import { useProductFilters, ProductFiltersProvider } from "@modules/attributes/components/product-filters-context";

export const AttributeFilters = React.memo((props: ProductFiltersProps) => {
    const { className } = props;
    const {
      setAttributes,
      attributes,
      intAttributes,
      setIntAttributes,
    } = useProductFilters(); 

    // Custom hook to fetch "/store/attributes" route
    const { attributes: customAttributes } = useAttributes();

    return (
      <VStack
        className={classnames(cls.AttributeFilters, {}, [className])}
        gap="32"
      >
        {customAttributes?.map((attribute) => {
          if (attribute.type === "boolean") {
            const checked = attributes?.[attribute.handle] ?? false;

            return (
              <Checkbox
                key={attribute.id}
                name={attribute.name}
                checked={!!checked}
                onChange={() => {
                  if (checked) {
                    const newAttributes = { ...attributes };
                    delete newAttributes[attribute.handle];
                    setAttributes?.(newAttributes);
                  } else {
                    setAttributes?.((prev) => ({
                      ...prev,
                      [attribute.handle]: [attribute.values[0].id],
                    }));
                  }
                }}
              >
                {attribute.name}
              </Checkbox>
            );
          }

          if (attribute.type === "range") {
            return (
              <Range
                name={attribute.name}
                maxValue={100}
                minValue={0}
                key={attribute.id}
                setValues={(value) => {
                  setIntAttributes?.((prev) => ({
                    ...prev,
                    [attribute.id]: value,
                  }));
                }}
                values={[
                  intAttributes?.[attribute.id]?.[0] ?? 0,
                  intAttributes?.[attribute.id]?.[1] ?? 100,
                ]}
              />
            );
          }

          return (
            <CheckboxList
              key={attribute.id}
              checked={attributes?.[attribute.handle] ?? []}
              label={attribute.name}
              options={attribute.values.map((value) => ({
                value: value.id,
                label: value.value,
              }))}
              onChange={(values) =>
                setAttributes?.((prev) => ({
                  ...prev,
                  [attribute.handle]: values,
                }))
              }
            />
          );
        })}
      </VStack>
    );
  });

Now I try implement this filter directly in base category template: https://github.com/medusajs/nextjs-starter-medusa/blob/main/src/modules/categories/templates/index.tsx

add line: import { AttributeFilters } from "@modules/attributes/components/attribute-filters";

But everytime I get:

Unhandled Runtime Error
ReferenceError: useAttributes is not defined

Source
src\modules\attributes\components\attribute-filters\index.tsx (16:45) @ useAttributes

  14 | 
  15 |   // Custom hook to fetch "/store/attributes" route
> 16 |   const { attributes: customAttributes } = useAttributes();

Can you help/give advice on how we can implement this filter directly into the category template?

I can't find information with the definition of useAttributes() anywhere.

vholik commented 7 months ago

It's not an issue with the plugin itself but I will try to help you with that. useAttributes() is a custom hook to fetch /store/attributes. For this particular example I'm using SWR but you can use whatever fetch abstraction you like. Here is an example of code:

// use-attributes.ts
import { $api } from "@/shared/api";
import useSWR, { Fetcher } from "swr";

const fetcher = () =>
  $api
    .get(`store/attributes`)
    .then((res) => res.data);

export const ATTRIBUTES_KEY = `store/attributes`;

export const useAttributes = () => {
  const { data, error, isLoading } = useSWR(
    ATTRIBUTES_KEY,
   fetcher
  );

  return {
    attributes: data,
    error,
    isLoading,
  };
};
Sylvesterx1 commented 7 months ago

@vholik Thank you for the information. Could you provide us with some sample files containing integration with visible imported components?

For UI we try use

 import { VStack, Checkbox, CheckboxGroup } from "@chakra-ui/react";
import classnames from 'classnames';

but chakra-ui not have CheckboxList so we create manual component. import CheckboxList from "@modules/attributes/components/checkbox-list";

but the output filter is: image

Not possible filter products. (not clickable) So if you could share some files with us somewhere to see how it works witk all compoentns or send them to brandwrox1@gmail.com, I would be grateful.