Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue: Not able to return object incase of Multiselect field #127

Open
priya-singh07 opened this issue Sep 28, 2024 · 1 comment
Open

Issue: Not able to return object incase of Multiselect field #127

priya-singh07 opened this issue Sep 28, 2024 · 1 comment

Comments

@priya-singh07
Copy link

Describe the bug
I have a usecase to return the array of object when I am selecting multiselect field but it is giving me error as "Cannot read properties of undefined (reading 'map')"

To Reproduce
Steps to reproduce the behavior:

  1. Create one more multiselect storybook field in your existing code which will return the object
  2. Go to 'storybook'
  3. Open the storybook component which you created
  4. See error

Expected behavior
I want to submit the multiselect in form of object.

Screenshots
image

I took one of your given example of countryOptions:

export const InitializedObject = formStory({
name: "Initialized Required Array<object>",
  args: {
    fields: {
      countries: zodField({
        value: undefined,
        schema: z.array(z.object(
          { name: z.string(), key: z.string() },
          { required_error: "Please select country." },
        )),
      }),
    },
    children: ({ fields }) => (
      <MultiSelectField
        initialValue={[countryOptions[0]]}
        field={fields.countries as ZodArrayField}
        label="Visited countries"
        options={countryOptions} 
        getValue={(option) => option}
        getLabel={({ name }) => name}
      />
    ),
  },
});

Please Note: I tried changing the useMultiSelectFieldProps and compared object using deep equal and some null checks so it is working for me. Do have a look on this:

import` { UseFieldOptions } from "form-atoms";

import { useAtomValue } from "jotai";
import {isEqual as deepEqual} from 'lodash-es';
import { ChangeEvent, useCallback, useMemo, useRef } from "react";
import { ArrayCardinality, ZodAny, ZodArray } from "zod";

import { UseOptionsProps, useFieldProps } from "..";
import { ZodArrayField, ZodField, ZodFieldValue } from "../../fields";

export type UseMultiSelectFieldProps<Option, Field extends ZodArrayField> = {
  field: Field;
  getValue: (option: Option) => ZodArrayFieldValue<Field>;
} & Pick<UseOptionsProps<Option>, "options">;

export type ZodArrayFieldValue<Field> =
  Field extends ZodField<
    ZodArray<infer Value, ArrayCardinality>,
    ZodArray<ZodAny, ArrayCardinality>
  >
    ? Value["_output"]
    : never;

export const useMultiSelectFieldProps = <Option, Field extends ZodArrayField>(
  { field, options, getValue }: UseMultiSelectFieldProps<Option, Field>,
  fieldOptions?: UseFieldOptions<ZodFieldValue<Field>>,
) => {
  const atom = useAtomValue(field);
  const fieldValue = useAtomValue(atom.value);
  const optionValues = useMemo(() => options.map(getValue),[getValue, options]);
  const prevValue = useRef(fieldValue);

  const activeIndexes = useRef<string[]>(
    fieldValue?.map((activeOption) =>
      optionValues.findIndex((optionValue) => deepEqual(optionValue, activeOption)).toString(),
    ),
  );

  if (deepEqual(prevValue.current, fieldValue)) {
    activeIndexes.current = fieldValue?.map((activeOption) =>
      optionValues.findIndex((optionValue) => deepEqual(optionValue, activeOption)).toString(),
    );
  }

  const getEventValue = useCallback(
    (event: ChangeEvent<HTMLSelectElement>) => {
      const nextIndexes = Array.from(event.currentTarget.options)
        .filter((option) => option.selected)
        .map((option) => option.value);

      activeIndexes.current = nextIndexes;
      const nextValues = nextIndexes.map((index) => optionValues[parseInt(index)]);
      prevValue.current = nextValues;
      return nextValues as ZodFieldValue<Field>;
    },
    [optionValues],
  );
  const props = useFieldProps<Field, HTMLSelectElement>(field, getEventValue, fieldOptions);
  return { ...props, value: activeIndexes.current };
};

Working screenshot:

image
@pgangwani
Copy link

@MiroslavPetrik Would you be able to help with this ? For now we copied the hooked and added deepEqual when object comparision if getValue returns array of object

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants