React Hook Form and Zod validation in Bryntum Scheduler Pro Bryntum, a scheduling component vendor, released a guide and GitHub repository demonstrating how to replace the built-in task editor in Bryntum Scheduler Pro with a custom React Hook Form dialog and Zod validation schema. The tutorial covers setting up a Vite React TypeScript app, installing Scheduler Pro trial and React Hook Form, and integrating a Bryntum date widget while moving validation rules into a Zod schema. React Hook Form and Zod validation in Bryntum Scheduler Pro We strive to keep posts updated, but code samples may sometimes be outdated. Humans, see the Bryntum documentation ; agents, https://mcp.bryntum.com for the latest info. Bryntum’s suite of scheduling components is full of forms including task, event, resource, and calendar editors. The Bryntum Scheduler Pro https://bryntum.com/products/schedulerpro/ includes a task editor form with validation that can be customized, turned off, or replaced with a custom editor. Bryntum components come with over 100 widgets https://bryntum.com/products/schedulerpro/docs/api/widgets that you can use to modify the forms including DateField, Combo, and ColorPicker. You may want to replace the task editor with a custom one if you’re adding the Scheduler Pro to an app that uses UI library form components, such as Material UI, and a form validation library such as React Hook Form https://react-hook-form.com/ , the popular React form state management and validation library. Replacing the task editor can keep validation and form components consistent with the rest of the application. In this guide, we’ll show you how to customize and validate Scheduler Pro’s built-in task editor. We’ll then replace it with a React Hook Form dialog, show how to add a Bryntum date widget, and move the validation rules into a Zod https://zod.dev/ schema. You can find the code in the Bryntum Scheduler Pro React Hook Form and Zod GitHub repo https://github.com/bryntum/bryntum-scheduler-pro-react-hook-form-zod . Getting started: Setting up a React Bryntum Scheduler Pro Run the following commands in your terminal to create a Vite React TypeScript application and install Scheduler Pro, its React wrapper, and React Hook Form: npm create vite@latest schedulerpro-react-hook-form -- --template react-ts cd schedulerpro-react-hook-form npm install npm install @bryntum/schedulerpro@npm:@bryntum/schedulerpro-trial @bryntum/schedulerpro-react npm install react-hook-form If you have a Bryntum license, follow the npm repository guide https://bryntum.com/products/schedulerpro/docs/guide/SchedulerPro/npm-repository to access the private Bryntum repository and install @bryntum/schedulerpro instead of the trial package. Delete the Vite starter files that the tutorial does not use: rm src/App.css && rm -r src/assets Update the vite.config.ts file so Vite prebundles both Bryntum packages once during development: python import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig { plugins: react , optimizeDeps: { include: '@bryntum/schedulerpro', '@bryntum/schedulerpro-react' , }, } ; The optimizeDeps entry prevents Vite from processing the Scheduler Pro packages as separate dependency graphs during development. Replace the contents of src/index.css with the Bryntum structural CSS, icons, Svalbard light theme https://bryntum.com/products/schedulerpro/docs/guide/SchedulerPro/customization/styling using-a-theme , and the page sizing styles: @import "@bryntum/schedulerpro/fontawesome/css/fontawesome.css"; @import "@bryntum/schedulerpro/fontawesome/css/solid.css"; @import "@bryntum/schedulerpro/schedulerpro.css"; @import "@bryntum/schedulerpro/svalbard-light.css"; @import url "https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap" ; html, body, root { height: 100%; margin: 0; } body { font-family: 'Poppins', 'Segoe UI', Arial, sans-serif; } root { display: flex; flex-direction: column; } The first four imports provide Scheduler Pro’s structural styles, icons, and theme. The full-height flex layout makes the scheduler fill the viewport height. Loading the Scheduler Pro data We’ll load the resources, events, and assignments data from one local file. Create a data.json file in the public folder and add the following JSON to it: { "success": true, "resources": { "rows": { "id": 1, "name": "Dan Stevenson" }, { "id": 2, "name": "Talisha Babin" }, { "id": 3, "name": "Michael Chen" }, { "id": 4, "name": "Sophia Rodriguez" }, { "id": 5, "name": "Arjun Mehta" }, { "id": 6, "name": "Priya Nair" }, { "id": 7, "name": "Liam O'Connor" }, { "id": 8, "name": "Grace Kim" }, { "id": 9, "name": "Noah Fischer" }, { "id": 10, "name": "Isabella Costa" }, { "id": 11, "name": "Ethan Walker" }, { "id": 12, "name": "Maya Patel" } }, "events": { "rows": { "id": 1, "name": "Project Kickoff", "startDate": "2026-10-05", "duration": 2, "durationUnit": "d", "priority": "medium" }, { "id": 2, "name": "Requirement Gathering", "startDate": "2026-10-07", "duration": 4, "durationUnit": "d", "priority": "high" }, { "id": 3, "name": "UI/UX Design", "startDate": "2026-10-12", "duration": 5, "durationUnit": "d", "priority": "medium" }, { "id": 4, "name": "Backend Development", "startDate": "2026-10-19", "duration": 7, "durationUnit": "d", "priority": "high" }, { "id": 5, "name": "Frontend Development", "startDate": "2026-10-26", "duration": 6, "durationUnit": "d", "priority": "medium" }, { "id": 6, "name": "API Integration", "startDate": "2026-11-02", "duration": 4, "durationUnit": "d", "priority": "low" }, { "id": 7, "name": "Testing & QA", "startDate": "2026-11-06", "duration": 3, "durationUnit": "d", "priority": "high" }, { "id": 8, "name": "Final Deployment", "startDate": "2026-11-10", "duration": 2, "durationUnit": "d", "priority": "medium" }, { "id": 9, "name": "Stakeholder Review", "startDate": "2026-10-08", "duration": 2, "durationUnit": "d", "priority": "medium" }, { "id": 10, "name": "Data Migration", "startDate": "2026-10-14", "duration": 5, "durationUnit": "d", "priority": "high" }, { "id": 11, "name": "Security Audit", "startDate": "2026-10-21", "duration": 3, "durationUnit": "d", "priority": "high" }, { "id": 12, "name": "Performance Tuning", "startDate": "2026-10-27", "duration": 4, "durationUnit": "d", "priority": "medium" }, { "id": 13, "name": "Documentation", "startDate": "2026-11-03", "duration": 3, "durationUnit": "d", "priority": "low" }, { "id": 14, "name": "User Training", "startDate": "2026-11-09", "duration": 2, "durationUnit": "d", "priority": "medium" }, { "id": 15, "name": "Vendor Onboarding", "startDate": "2026-10-06", "duration": 3, "durationUnit": "d", "priority": "low" }, { "id": 16, "name": "Infrastructure Setup", "startDate": "2026-10-13", "duration": 6, "durationUnit": "d", "priority": "high" }, { "id": 17, "name": "Load Testing", "startDate": "2026-10-22", "duration": 3, "durationUnit": "d", "priority": "medium" }, { "id": 18, "name": "Accessibility Review", "startDate": "2026-10-29", "duration": 2, "durationUnit": "d", "priority": "low" }, { "id": 19, "name": "Localization", "startDate": "2026-11-04", "duration": 4, "durationUnit": "d", "priority": "medium" }, { "id": 20, "name": "Go-Live Support", "startDate": "2026-11-11", "duration": 3, "durationUnit": "d", "priority": "high" }, { "id": 21, "name": "Design Handoff", "startDate": "2026-10-06", "duration": 2, "durationUnit": "d", "priority": "medium" }, { "id": 22, "name": "Analytics Setup", "startDate": "2026-10-15", "duration": 3, "durationUnit": "d", "priority": "low" }, { "id": 23, "name": "Payment Integration", "startDate": "2026-10-20", "duration": 5, "durationUnit": "d", "priority": "high" }, { "id": 24, "name": "Regression Testing", "startDate": "2026-11-05", "duration": 3, "durationUnit": "d", "priority": "medium" } }, "assignments": { "rows": { "id": 1, "event": 1, "resource": 1 }, { "id": 2, "event": 2, "resource": 2 }, { "id": 3, "event": 3, "resource": 3 }, { "id": 4, "event": 4, "resource": 4 }, { "id": 5, "event": 5, "resource": 1 }, { "id": 6, "event": 6, "resource": 2 }, { "id": 7, "event": 7, "resource": 3 }, { "id": 8, "event": 8, "resource": 4 }, { "id": 9, "event": 9, "resource": 5 }, { "id": 10, "event": 10, "resource": 6 }, { "id": 11, "event": 11, "resource": 7 }, { "id": 12, "event": 12, "resource": 8 }, { "id": 13, "event": 13, "resource": 5 }, { "id": 14, "event": 14, "resource": 6 }, { "id": 15, "event": 15, "resource": 9 }, { "id": 16, "event": 16, "resource": 10 }, { "id": 17, "event": 17, "resource": 11 }, { "id": 18, "event": 18, "resource": 12 }, { "id": 19, "event": 19, "resource": 9 }, { "id": 20, "event": 20, "resource": 10 }, { "id": 21, "event": 21, "resource": 7 }, { "id": 22, "event": 22, "resource": 8 }, { "id": 23, "event": 23, "resource": 11 }, { "id": 24, "event": 24, "resource": 12 } } } The Bryntum Scheduler Pro’s project model https://bryntum.com/products/schedulerpro/docs/api/SchedulerPro/model/ProjectModel reads these three stores from the same response. Assignments connect each event to a resource using their IDs. Creating a custom event model The data includes a custom priority value. We’ll add it to the Bryntum task model by defining a custom event model https://bryntum.com/products/schedulerpro/docs/api/SchedulerPro/model/EventModel . Create a TaskModel.ts file in src/lib : mkdir src/lib && touch src/lib/TaskModel.ts Add the following lines of code to it: js import { EventModel } from '@bryntum/schedulerpro'; export const taskPriorities = 'low', 'medium', 'high' as const; export type TaskPriority = typeof taskPriorities number ; export default class TaskModel extends EventModel { declare priority: TaskPriority; static get fields { return { name: 'priority', defaultValue: 'medium' }, ; } } export type TaskRecord = InstanceType