Closed amirakbulut closed 4 months ago
I ran into this problem, while I have not found the props to do exactly what you describe, I used "value" a a combination of my label and unique ID. (This is Shadcn UI's Command component, but it uses cmdk inside)
<CommandDialog open={isOpen} onOpenChange={onClose}>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Documents">
{documents?.map((document) => (
<CommandItem
value={`${document._id}-${document.title}`}
title={document.title}
onSelect={() => onSelect(document._id)}
key={document._id}
>
<FileText className="mr-2 h-4 w-4" />
<span>{document.title}</span>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</CommandDialog>
This is definitely a step into better UX, but you can also search by "id" so users might get confused why they are seeing certain results if they type uuid for example haha
I'm with the same issue, I wanted to be able to search by the name, but I wanted to be able to assign the ID of the item when I the use select the item, but doing that searching is disabled. I was doing something like this, it's in chadcn ui but the underline code is cmdk, the selecting part is working but the search part is broken
<Command>
<CommandInput placeholder="Procurar exercício..." />
<CommandEmpty>Nenhum exercício encontrado.</CommandEmpty>
<CommandGroup>
{exercicios.map((exercicio) => (
<CommandItem
key={exercicio.id}
onSelect={(currentValue) => {
setValue(currentValue === value ? "" : currentValue)
setOpen(false)
}}
value={exercicio.id}
title={exercicio.nome}
>
<Check
className={cn(
"mr-2 h-4 w-4",
value === exercicio.id ? "opacity-100" : "opacity-0"
)}
/>
{exercicio.nome}
</CommandItem>
))}
</CommandGroup>
</Command>
I would love to do this as well!
I think you can disable filter totally and make state based custom filter by yourself.
<Command shouldFilter={false}>
This is so much easier
here is my solution, using Shadcn/ui
Key points"
${item.name}
}${currentValue?.id}
"use client";
import React, { use, useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Check, ChevronsUpDown } from "lucide-react";
import { cn } from "@/lib/utils";
export interface DropdownListItem {
id: number;
name: string;
}
export interface DropdownProps {
list: DropdownListItem[];
}
export default function DropdownCommand({ list }: DropdownProps) {
const [open, setOpen] = useState(false);
const [value, setValue] = useState("");
const [search, setSearch] = useState("");
useEffect(() => {
if (open) {
setSearch("");
}
}, [open]);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="secondary"
role="combobox"
aria-expanded={open}
className="w-[350px] justify-between"
>
{value
? list.find((item) => `${item.id}` === value)?.name
: "Choisir une catégorie"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandInput
placeholder="Search framework..."
className="h-9"
value={search}
onValueChange={setSearch}
/>
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
{list.map((item) => (
<CommandItem
key={`${item.id}`}
value={`${item.name}`}
title={item.name}
onSelect={() => {
const currentValue = list.find((el) => el.id === item.id);
setValue(
`${currentValue?.id}` === value
? ""
: `${currentValue?.id}`,
);
setOpen(false);
}}
>
{item.name}
<Check
className={cn(
"ml-auto h-4 w-4",
value === `${item.id}` ? "opacity-100" : "opacity-0",
)}
/>
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
);
}
+1, also currently bumping into the same issue. I think there should be an option to define the search key.
Great job with this component works really well! 🎉
Initially I thought a search key could be useful but in most cases the filter
prop in the Command
component could solve the use case of having the ID
as a value
and wanting to look up by some other key. e.g. name
.
In my use case I have an array of teams
being passed to the component and command item makes use of the team.id
value which is then passed to the form onSelect
. This works just fine.
However, the CommandInput
seems to default search to team.id
as well which makes sense since that's the value selected but the user is interested in look up team names by team.name
.
We could argue that having a custom search key would help in this case, or something like searchValue
:
<CommandItem
key={item.id}
value={item.id}
searchValue={item.name} // 👈 Instead of using the id for search we are using the name instead.
onSelect={(currentValue) => {
const newValue =
currentValue === value
? ''
: currentValue
setValue(newValue)
setOpen(false)
handleChange(newValue)
}}
>
Even if this works (I am not sure how internally the filtering occurs) it feels like we can't control a lot of how the filtering works. Using the filter prop
I was able to keep the ID
as a value to use it in the form and use the name for the search:
<Command
// Custom filter so that search looks up by name instead of id
filter={(value, search) => {
const teamName =
teams
?.find((item) => item.id === value)
?.name?.toLowerCase() || ''
if (teamName.includes(search)) return 1
return 0
}}
>
So, I just pushed all strings that I need to be searchable to the value, and set the id manually onSelect:
<CommandItem
key={item.id}
value={`${item.name} - ${item.nativeName}`}
onSelect={() => {
onChange(item.id)
setOpen(false)
}}
/>
Yeah, I think using value
was a mistake. I'll consider how to fix this for the next release. In the meantime, you should be able to use keywords
to add additional matching strings, in case that's helpful.
@pacocoursey I found a (in my opinion - works well for me) solution using an id-value map. I made a fork for work and could prepare a small PR within the next hours if you think that an id-value map would be cool, at least as a work-around.
PS: I just saw that you added keywords only today - that's not a big difference to what I did using a map. PSS: Although, adding the ID to the keywords might have performance impact on the search/filter algorithm? and could cause unwanted results (e.g., when using not numeric IDs).
Therefore, I would suggest adding an optional ID value to the item component or using a map provided on the root component where users can assign the ID to the value and the filter will get the text value of that item, but the value prop of the root component would be the ID of the item(s).
I think, the ID prop should be the preferred solution, because the map would rely on the value prop of the item to be the ID (which works in my use cases, but may not for everyone).
I would be willing to work on that and open a PR this week.
I noticed that version 0.2.1 still doesn't have keywords
Same, I'm trying to use keywords to circumvent the search key problem like suggested here and I see the PR is merged. But 0.2.1 does not include it @pacocoursey
+1
If anyone looking for a safe workaround, here's mine to consider.
Problem:
id-name
as value makes the search less accurate especially if the id
is some unique string id.id-name
is hard since i cant just split based on "-" (my id can have that).
id
length to a fixed number but it's still feel weird setting a random fixed length for id
id
for value, it will be lowercase in the filter.Solution:
// use this as Command.Item value
const stringValue = JSON.stringify({
id: item.id,
name: item.name,
}).replaceAll('"', "'");
// replaceAll is needed because it won't work with double quotes
```tsx
// for the filter function
<Command
filter={(value, search) => {
const { name } = JSON.parse(value.replaceAll("'", '"')) as { id: string; name: string; };
if (name?.includes(search)) return 1;
return 0;
}}
></Command>;
Note that this is just a temporary workaround. As soon as the keywords
prop available, I'll switch to it.
You can refer my full implementation here
I just now ran into a similar situation too and my mind immediately went to "how do I set the search key?" But after working with this for a bit I think I prefer how cmdk
does this: just set a custom filter. This lets you, the implementer, decide how you want it to work when the defaults don't do what you want.
Here's a contrived example.
import { Command, CommandGroup, CommandItem } from "./ui/command"
type Car = {
id: string
name: string
}
type CarsDropdownProps = {
cars: Car[]
}
function CarsDropdown({ cars }: CarsDropdownProps) {
// Create a map to avoid O(n) lookups
const carsMap = cars.reduce(
(acc, car) => {
acc[car.id] = car // Use the unique identifier as the key
return acc
},
{} as Record<string, Car>
)
return (
<Command
filter={(value, search) => {
const car = carsMap[value]
// Basic search but you can extend this
if (car && car.name.toLowerCase().includes(search.toLowerCase())) return 1
return 0
}}
>
<CommandGroup>
{cars.map((car) => (
<CommandItem key={car.id} value={car.id}>
{car.name}
</CommandItem>
))}
</CommandGroup>
</Command>
)
}
Any update on this?
keywords
prop is available in v1.0.0
.
First of all, thank you for all the great work.
In a lot of cases you query your API by
id
instead ofname
, because of that we set thevalue
property of selectable items to be equal to theirid
.The component however, compares search queries against this
value
property, which is totally fine and expected, but it requires us to search our items byid
instead of readable names. Which is not that user friendly.Is there a way to search by a specific property, but have the
value
property still be equal toid
?Maybe something like
value="id" searchValue="name"