Customization & Optimization
Customization & Optimization setup of tags input with shadecn and tailwind.
Error Handling
With the onError
prop, you can handle errors when adding tags.
const handleError = (code: TagErrorCode) => {
switch (code) {
case "E01":
console.log("E01: Tag already exists");
break;
case "E02":
console.log("E02: Tag not found in suggestions");
break;
}
};
Client component
- Create a client component:
"use client";
import { useState } from "react";
import { Tag, TagErrorCode, TagsInput } from "@/components/ui/tags-input";
export default function Example() {
const [selectedTags, setSelectedTags] = useState<Tag[]>([]);
const suggestions: Tag[] = [
{ id: "1", name: "JavaScript" },
{ id: "2", name: "TypeScript" },
{ id: "3", name: "React" },
];
const handleError = (code: TagErrorCode) => {
switch (code) {
case "E01":
console.log("E01: Tag already exists");
break;
case "E02":
console.log("E02: Tag not found in suggestions");
break;
}
};
return (
<TagsInput
value={selectedTags}
onChange={setSelectedTags}
suggestions={suggestions}
onlyFromSuggestions={true}
maxTags={3}
onError={handleError}
placeholder="add tag & press enter"
className="bg-neutral-50 rounded-xl"
inputClassName=""
badgeClassName=""
badgeCloseClassName="hover:bg-red-500"
autocompleteClassName=""
/>
);
}
Custom autocomplete
You can customize the component with tailwindcss classes.
In autocompleteClassName, you can use the following blocks:
- div
- ul
- ul>li
// Default style in autocompleteClassName
<div
className={cn(
"absolute z-10 w-full mt-1 bg-popover border rounded-md shadow-md",
autocompleteClassName,
"[&>ul]:py-1",
"[&>ul>li]:px-3 [&>ul>li]:py-2 [&>ul>li]:cursor-pointer"
)}
/>
With zod
Define a schema
export const schemaTags = z.object({
tags: z
.array(
z.object({
id: z.string(),
name: z.string(),
})
)
.optional()
.default([]),
});
Use the schema in a form
const defaultFormValuesTags = {
tags: [],
};
const form = useForm<z.infer<typeof schemaTags>>({
resolver: zodResolver(schemaTags),
defaultValues: defaultFormValuesTags,
});
Use the form in a component
<FormField
control={form.control}
name="tags"
render={({ field }) => (
<TagsInput
value={field.value}
onChange={field.onChange}
suggestions={suggestions}
onlyFromSuggestions={true}
placeholder="add tag"
/>
)}
/>
Output the form
[Log] selectedTags – [{id: "1", name: "JavaScript"}] (1)
[Log] selectedTags – [{id: "1", name: "JavaScript"}, {id: "3", name: "React"}] (2)
Change placeholder
Change placeholder with form shadecn.
import React, { useState, useEffect } from "react";
import { Tag, TagsInput } from "@/components/ui/tags-input";
import { useForm } from "react-hook-form";
export default function ChangePlaceholder() {
const form = useForm();
const [maxTags, setMaxTags] = useState(3);
const [placeholder, setPlaceholder] = useState("add tag");
const suggestions: Tag[] = [
{ id: "1", name: "JavaScript" },
{ id: "2", name: "TypeScript" },
{ id: "3", name: "React" },
];
useEffect(() => {
// Update placeholder based on the number of tags
const currentTags = form.watch("tags").length;
if (currentTags === 0) {
setPlaceholder(`add ${maxTags} max`);
} else if (currentTags < maxTags) {
setPlaceholder(`add more ${maxTags - currentTags} tags ...`);
} else {
setPlaceholder("no more tags");
}
}, [form.watch("tags"), maxTags]);
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col space-y-6 text-left "
>
<FormField
control={form.control}
name="tags"
render={({ field }) => (
<TagsInput
value={field.value}
onChange={field.onChange}
suggestions={suggestions}
onlyFromSuggestions={false}
maxTags={maxTags}
placeholder={placeholder}
className=""
inputClassName=""
badgeClassName=""
badgeCloseClassName=""
autocompleteClassName=""
/>
)}
/>
</form>
</Form>
);
}
Fetch suggestions
use Tag Input with a api routes for fetch suggestions.
version > 1.3.1
Added description for dynamic fetching This is an example of dynamic fetching on every change in the input field, which fetches data using UseSWR and refreshes the list of tags with mutate.
// route.ts with prisma
const searchTerm = req.nextUrl.searchParams.get("search") || "";
const tags = await db.tags.findMany({
where: {
name: {
contains: searchTerm,
mode: "insensitive",
},
},
select: {
id: true,
name: true,
},
});
return NextResponse.json(tags);
composant.tsx
const [searchTerm, setSearchTerm] = useState("");
const {
data: suggestions,
error: suggestionsError,
isLoading: suggestionsLoading,
mutate: mutateSuggestions,
} = useSWR<Tag[]>(`/api/link/tags/search?search=${searchTerm}`, fetcher);
// listen search tag key
useEffect(() => {
if (searchTerm) {
mutateSuggestions();
}
}, [searchTerm, mutateSuggestions]);
<TagsInput
value={field.value}
onChange={field.onChange}
suggestions={suggestions}
onlyFromSuggestions={false}
maxTags={maxTags}
placeholder={placeholder}
onInputChange={(value: string) => setSearchTerm(value)}
/>;