Search code examples
reactjstypescriptnext.jsyupheadless-ui

Yup doesn't get headlessui combobox value


I'm using the Headlessui combobox component but I had a problem when using it with Yup. The value I select with the combobox is written to the selectedMemory state variable without any problem, but Yup still throws a required error message. There must be a point I'm missing.

types for form

// form values for Yup
type RewardCreateFormValues = {
    name: string
    price: number
    quantity: number
    reward: string
}

type Reward = {
    id: string
    name: string
    price: number
    quantity: number
}

const rewards: Reward[] = [
    {
        id: "1",
        name: "Reward 1",
        price: 100,
        quantity: 100
    },
    {
        id: "2",
        name: "Reward 2",
        price: 200,
        quantity: 200
    },
    {
        id: "3",
        name: "Reward 3",
        price: 300,
        quantity: 300
    }
]

Created states and memoized filtered reward for combobox

    const [selectedReward, setSelectedReward] = useState(rewards[0])
    const [query, setQuery] = useState("")

    // it filters the rewards according to the query
    let filteredReward =
        query === ""
            ? rewards
            : rewards.filter(reward => {
                  return reward.name.toLowerCase().includes(query.toLowerCase())
              })

    const rewardOptions = useMemo(() => {
        return filteredReward.map((reward: Reward) => ({
            name: reward.name,
            id: reward.id
        }))
    }, [filteredReward])

object validation with Yup and form initial values

    const validationSchema = Yup.object().shape({
        name: Yup.string().required("Required"),
        price: Yup.number().required("Required"),
        quantity: Yup.number().required("Required"),
        reward: Yup.string().required("Required")
    })

    const initialValues: RewardCreateFormValues = {
        name: "",
        price: 0,
        quantity: 0,
        reward: ""
    }

And combobox;

                           <GridItem colSpan={3}>
                                <FormControl>
                                    <Box>
                                        <FormLabel>Select a Reward</FormLabel>
                                        <Combobox as="div" value={selectedReward} onChange={setSelectedReward}>
                                            <Combobox.Button as="div">
                                                <Combobox.Input
                                                    className="w-full rounded-md border-0 bg-white py-2 pl-3 pr-12 text-gray-900 shadow-sm ring-1 ring-inset ring-[#E2E8F0] focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                                                    name="reward"
                                                    onChange={(event: any) => setQuery(event.target.value)}
                                                    placeholder="Select a Reward"
                                                    displayValue={(selectedReward: Reward) =>
                                                        selectedReward ? selectedReward.name : ""
                                                    }
                                                />
                                            </Combobox.Button>
                                            <Combobox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
                                                {rewardOptions.map(reward => (
                                                    <Combobox.Option
                                                        key={reward.id}
                                                        value={reward}
                                                        className={({ active }) =>
                                                            classNames(
                                                                "relative cursor-default select-none py-2 pl-3 pr-9",
                                                                active ? "bg-indigo-600 text-white" : "text-gray-900"
                                                            )
                                                        }
                                                    >
                                                        {({ active, selected }) => (
                                                            <>
                                                                <Box display={"flex"}>
                                                                    <Text
                                                                        as={"span"}
                                                                        className={classNames(
                                                                            "truncate",
                                                                            selected ? "font-semibold" : "font-normal"
                                                                        )}
                                                                    >
                                                                        {reward.name}
                                                                    </Text>
                                                                </Box>

                                                                {selected && (
                                                                    <Text
                                                                        as={"span"}
                                                                        className={classNames(
                                                                            "absolute inset-y-0 right-0 flex items-center pr-4",
                                                                            active ? "text-white" : "text-indigo-600"
                                                                        )}
                                                                    >
                                                                        <CheckIcon
                                                                            className="h-5 w-5"
                                                                            aria-hidden="true"
                                                                        />
                                                                    </Text>
                                                                )}
                                                            </>
                                                        )}
                                                    </Combobox.Option>
                                                ))}
                                                <Combobox.Option
                                                    value={""}
                                                    className={
                                                        "relative cursor-default select-none py-2 pl-3 pr-9 bg-indigo-100 hover:bg-indigo-600 hover:text-white text-gray-900"
                                                    }
                                                    onClick={onOpen}
                                                >
                                                    <Box
                                                        display={"flex"}
                                                        flexDirection={"row"}
                                                        gap={2}
                                                        alignItems={"center"}
                                                    >
                                                        <PlusIcon className="h-5 w-5" aria-hidden="true" />
                                                        <button>Create a Reward</button>
                                                    </Box>
                                                </Combobox.Option>
                                            </Combobox.Options>
                                        </Combobox>
                                    </Box>
                                    <ErrorMessage name="reward" component="p" className="mt-2 text-sm text-red-600" />
                                </FormControl>
                            </GridItem>
                            <GridItem colSpan={3}>
                                <Box display={"flex"} justifyContent="flex-end">
                                    <Button
                                        isLoading={isSubmitting}
                                        isDisabled={isSubmitting}
                                        type="submit"
                                        colorScheme="indigo"
                                        variant="solid"
                                    >
                                        Create Listing
                                    </Button>
                                </Box>
                            </GridItem>

As I mentioned above, the problem is that Yup keeps throwing the required error even if the selectedMemory content and value change.


Solution

  • Headless/ui documentation is really bad. So I did some experimental development myself.

    • I was using yup but I was keeping seletedReward in a state, I removed the state there and instead I added values, setFieldValue values to the field in formic (outside of the code I shared) and I started to do the control with formic + yup and I updated the props accordingly.

    • I started to get the reward as an object in yup, not a string, for this I updated the validationSchema, formValues content.

    After doing these, it started to work smoothly, I hope it helped your problem. If you have a similar problem, I would like to help if you share it as a comment.