agentic-jumpstart-react

React 19 Best Practices

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "agentic-jumpstart-react" with this command: npx skills add webdevcody/agentic-jumpstart/webdevcody-agentic-jumpstart-agentic-jumpstart-react

React 19 Best Practices

Component Structure

File Organization

src/routes/admin/courses/ ├── route.tsx # Route definition ├── -components/ # Route-specific components │ ├── CourseList.tsx │ ├── CourseForm.tsx │ └── CourseCard.tsx

Component Pattern

import { type ReactNode } from "react";

interface CourseCardProps { course: Course; onEdit?: (id: number) => void; children?: ReactNode; }

export function CourseCard({ course, onEdit, children }: CourseCardProps) { return ( <Card> <CardHeader> <CardTitle>{course.title}</CardTitle> <CardDescription>{course.description}</CardDescription> </CardHeader> <CardContent>{children}</CardContent> {onEdit && ( <CardFooter> <Button onClick={() => onEdit(course.id)}>Edit</Button> </CardFooter> )} </Card> ); }

Form Handling with React Hook Form + Zod

Basic Form Pattern

import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod";

const formSchema = z.object({ title: z.string().min(1, "Title is required").max(100), description: z.string().max(500).optional(), isPremium: z.boolean().default(false), });

type FormData = z.infer<typeof formSchema>;

export function CourseForm({ onSubmit }: { onSubmit: (data: FormData) => void }) { const form = useForm<FormData>({ resolver: zodResolver(formSchema), defaultValues: { title: "", description: "", isPremium: false, }, });

return ( <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> <div> <Label htmlFor="title">Title</Label> <Input id="title" {...form.register("title")} aria-invalid={!!form.formState.errors.title} /> {form.formState.errors.title && ( <p className="text-sm text-red-500"> {form.formState.errors.title.message} </p> )} </div>

  &#x3C;div>
    &#x3C;Label htmlFor="description">Description&#x3C;/Label>
    &#x3C;Textarea id="description" {...form.register("description")} />
  &#x3C;/div>

  &#x3C;div className="flex items-center gap-2">
    &#x3C;Checkbox
      id="isPremium"
      checked={form.watch("isPremium")}
      onCheckedChange={(checked) =>
        form.setValue("isPremium", checked === true)
      }
    />
    &#x3C;Label htmlFor="isPremium">Premium content&#x3C;/Label>
  &#x3C;/div>

  &#x3C;Button type="submit" disabled={form.formState.isSubmitting}>
    {form.formState.isSubmitting ? "Saving..." : "Save"}
  &#x3C;/Button>
&#x3C;/form>

); }

Data Fetching with TanStack Query

Query Options Pattern

import { queryOptions, useQuery, useSuspenseQuery } from "@tanstack/react-query";

// Define query options (can be shared between components) export const courseQueryOptions = (courseId: string) => queryOptions({ queryKey: ["course", courseId], queryFn: () => getCourseFn({ data: { courseId } }), });

// In component - with suspense function CourseDetails({ courseId }: { courseId: string }) { const { data: course } = useSuspenseQuery(courseQueryOptions(courseId)); return <div>{course.title}</div>; }

// In component - without suspense function CourseDetails({ courseId }: { courseId: string }) { const { data: course, isLoading, error } = useQuery(courseQueryOptions(courseId));

if (isLoading) return <Skeleton />; if (error) return <ErrorDisplay error={error} />; return <div>{course.title}</div>; }

Mutations with Optimistic Updates

import { useMutation, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner";

export function useUpdateCourse() { const queryClient = useQueryClient();

return useMutation({ mutationFn: updateCourseFn, onSuccess: () => { toast.success("Course updated successfully"); queryClient.invalidateQueries({ queryKey: ["courses"] }); }, onError: (error) => { toast.error(error.message || "Failed to update course"); }, }); }

Loading States & Suspense

Loading Skeleton Pattern

function CourseSkeleton() { return ( <Card> <CardHeader> <Skeleton className="h-6 w-3/4" /> <Skeleton className="h-4 w-1/2" /> </CardHeader> <CardContent> <Skeleton className="h-20 w-full" /> </CardContent> </Card> ); }

function CourseList() { return ( <Suspense fallback={<CourseSkeleton />}> <CourseListContent /> </Suspense> ); }

Error Boundaries

Route-Level Error Handling

import { DefaultCatchBoundary } from "~/components/DefaultCatchBoundary";

export const Route = createFileRoute("/courses/$courseId")({ component: CoursePage, errorComponent: DefaultCatchBoundary, });

Dialog & Modal Pattern

import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "~/components/ui/dialog";

function DeleteCourseDialog({ course, onDelete, }: { course: Course; onDelete: () => void; }) { const [open, setOpen] = useState(false);

return ( <Dialog open={open} onOpenChange={setOpen}> <DialogTrigger asChild> <Button variant="destructive">Delete</Button> </DialogTrigger> <DialogContent> <DialogHeader> <DialogTitle>Delete Course</DialogTitle> <DialogDescription> Are you sure you want to delete "{course.title}"? This action cannot be undone. </DialogDescription> </DialogHeader> <DialogFooter> <Button variant="outline" onClick={() => setOpen(false)}> Cancel </Button> <Button variant="destructive" onClick={() => { onDelete(); setOpen(false); }} > Delete </Button> </DialogFooter> </DialogContent> </Dialog> ); }

Toast Notifications with Sonner

import { toast } from "sonner";

// Success toast toast.success("Course saved successfully");

// Error toast toast.error("Failed to save course");

// Promise toast toast.promise(saveCourse(data), { loading: "Saving course...", success: "Course saved!", error: "Failed to save course", });

// Custom toast with action toast("Course deleted", { action: { label: "Undo", onClick: () => restoreCourse(courseId), }, });

Animation with Framer Motion

Basic Animation

import { motion } from "framer-motion";

function FadeInCard({ children }: { children: ReactNode }) { return ( <motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }} transition={{ duration: 0.2 }} > {children} </motion.div> ); }

Staggered List Animation

import { motion } from "framer-motion";

const container = { hidden: { opacity: 0 }, show: { opacity: 1, transition: { staggerChildren: 0.1 }, }, };

const item = { hidden: { opacity: 0, y: 20 }, show: { opacity: 1, y: 0 }, };

function AnimatedList({ items }: { items: Item[] }) { return ( <motion.ul variants={container} initial="hidden" animate="show"> {items.map((item) => ( <motion.li key={item.id} variants={item}> {item.title} </motion.li> ))} </motion.ul> ); }

Drag and Drop with @hello-pangea/dnd

import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd";

function ReorderableList({ items, onReorder, }: { items: Item[]; onReorder: (items: Item[]) => void; }) { const handleDragEnd = (result: DropResult) => { if (!result.destination) return;

const reordered = Array.from(items);
const [removed] = reordered.splice(result.source.index, 1);
reordered.splice(result.destination.index, 0, removed);

onReorder(reordered);

};

return ( <DragDropContext onDragEnd={handleDragEnd}> <Droppable droppableId="items"> {(provided) => ( <ul {...provided.droppableProps} ref={provided.innerRef}> {items.map((item, index) => ( <Draggable key={item.id} draggableId={String(item.id)} index={index}> {(provided) => ( <li ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} > {item.title} </li> )} </Draggable> ))} {provided.placeholder} </ul> )} </Droppable> </DragDropContext> ); }

Custom Hooks

useLocalStorage

function useLocalStorage<T>(key: string, initialValue: T) { const [storedValue, setStoredValue] = useState<T>(() => { if (typeof window === "undefined") return initialValue; try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch { return initialValue; } });

const setValue = (value: T | ((val: T) => T)) => { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); window.localStorage.setItem(key, JSON.stringify(valueToStore)); };

return [storedValue, setValue] as const; }

useDebounce

function useDebounce<T>(value: T, delay: number): T { const [debouncedValue, setDebouncedValue] = useState<T>(value);

useEffect(() => { const handler = setTimeout(() => setDebouncedValue(value), delay); return () => clearTimeout(handler); }, [value, delay]);

return debouncedValue; }

Component Patterns Checklist

  • Components use TypeScript interfaces for props

  • Forms use React Hook Form with Zod validation

  • Data fetching uses TanStack Query patterns

  • Loading states have skeleton placeholders

  • Dialogs use shadcn/ui Dialog component

  • Toasts use Sonner for notifications

  • Animations use Framer Motion

  • Effects clean up subscriptions

  • Route-specific components in -components/ subdirectory

  • Shared components in /src/components/

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

agentic-jumpstart-performance

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

agentic-jumpstart-dependency-management

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

openclaw-version-monitor

监控 OpenClaw GitHub 版本更新,获取最新版本发布说明,翻译成中文, 并推送到 Telegram 和 Feishu。用于:(1) 定时检查版本更新 (2) 推送版本更新通知 (3) 生成中文版发布说明

Archived SourceRecently Updated
Coding

ask-claude

Delegate a task to Claude Code CLI and immediately report the result back in chat. Supports persistent sessions with full context memory. Safe execution: no data exfiltration, no external calls, file operations confined to workspace. Use when the user asks to run Claude, delegate a coding task, continue a previous Claude session, or any task benefiting from Claude Code's tools (file editing, code analysis, bash, etc.).

Archived SourceRecently Updated