DevExtreme v24.2 is now available.

Explore our newest features/capabilities and share your thoughts with us.

Your search did not match any results.

React Scheduler - Virtual Scrolling

With virtual scrolling, you can improve the overall performance of your application and reduce load times when our Scheduler component is bound to a large data set. When virtual scrolling is enabled, our Scheduler only renders visible appointments. When an appointment leaves the viewport, the Scheduler removes it from the DOM.

To enable virtual scrolling mode, set the scrolling.mode property to "virtual".

Virtual scrolling is available for all views except "agenda".

Backend API
import React from 'react'; import Scheduler, { Resource, View, Scrolling } from 'devextreme-react/scheduler'; import { resources, generateAppointments } from './data.ts'; const currentDate = new Date(2021, 1, 2); const groups = ['humanId']; const startDay = new Date(2021, 1, 1); const endDay = new Date(2021, 1, 28); const startDayHour = 8; const endDayHour = 20; const appointments = generateAppointments(startDay, endDay, startDayHour, endDayHour); const App = () => ( <Scheduler dataSource={appointments} height={730} defaultCurrentView={'Timeline'} defaultCurrentDate={currentDate} startDayHour={startDayHour} endDayHour={endDayHour} cellDuration={60} showAllDayPanel={false} groups={groups}> <View type='timelineWorkWeek' name='Timeline' groupOrientation='vertical' /> <View type='workWeek' groupOrientation='vertical' /> <View type='month' groupOrientation='horizontal' /> <Resource fieldExpr='humanId' dataSource={resources} label='Employee' /> <Scrolling mode='virtual' /> </Scheduler> ); export default App;
import React from 'react'; import Scheduler, { Resource, View, Scrolling } from 'devextreme-react/scheduler'; import { resources, generateAppointments } from './data.js'; const currentDate = new Date(2021, 1, 2); const groups = ['humanId']; const startDay = new Date(2021, 1, 1); const endDay = new Date(2021, 1, 28); const startDayHour = 8; const endDayHour = 20; const appointments = generateAppointments(startDay, endDay, startDayHour, endDayHour); const App = () => ( <Scheduler dataSource={appointments} height={730} defaultCurrentView="Timeline" defaultCurrentDate={currentDate} startDayHour={startDayHour} endDayHour={endDayHour} cellDuration={60} showAllDayPanel={false} groups={groups} > <View type="timelineWorkWeek" name="Timeline" groupOrientation="vertical" /> <View type="workWeek" groupOrientation="vertical" /> <View type="month" groupOrientation="horizontal" /> <Resource fieldExpr="humanId" dataSource={resources} label="Employee" /> <Scrolling mode="virtual" /> </Scheduler> ); export default App;
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.tsx'; ReactDOM.render( <App />, document.getElementById('scheduler'), );
import { SchedulerTypes } from 'devextreme-react/scheduler'; type Appointment = SchedulerTypes.Appointment & { humanId: number; }; type Resource = { text: string; id: number; color: string; }; export const resources: Resource[] = [{ id: 0, text: 'David Carter', color: '#74d57b', }, { id: 1, text: 'Emma Lewis', color: '#1db2f5', }, { id: 2, text: 'Noah Hill', color: '#f5564a', }, { id: 3, text: 'William Bell', color: '#97c95c', }, { id: 4, text: 'Jane Jones', color: '#ffc720', }, { id: 5, text: 'Violet Young', color: '#eb3573', }, { id: 6, text: 'Samuel Perry', color: '#a63db8', }, { id: 7, text: 'Luther Murphy', color: '#ffaa66', }, { id: 8, text: 'Craig Morris', color: '#2dcdc4', }, { id: 9, text: 'Sandy Wood', color: '#c34cb9', }, { id: 10, text: 'Susan Bennett', color: '#3d44ec', }, { id: 11, text: 'Lilly Barnes', color: '#4ddcca', }, { id: 12, text: 'Marcus Price', color: '#2ec98d', }, { id: 13, text: 'David Stewart', color: '#3ff6ca', }, { id: 14, text: 'Joseph Smith', color: '#f665aa', }, { id: 15, text: 'Carter Wilson', color: '#d1c974', }, { id: 16, text: 'Wyatt Lopez', color: '#ff6741', }, { id: 17, text: 'John Long', color: '#ee53dc', }, { id: 18, text: 'Jack Rivera', color: '#795ac3', }, { id: 19, text: 'Victoria Adams', color: '#ff7d8a', }, { id: 20, text: 'Madison Anderson', color: '#4cd482', }, { id: 21, text: 'Luna Moore', color: '#9d67cc', }, { id: 22, text: 'Michael Bailey', color: '#5ab1ef', }, { id: 23, text: 'Jenny Powell', color: '#68e18f', }, { id: 24, text: 'Daniel Peterson', color: '#4dd155', }, { id: 25, text: 'Gabriel Gray', color: '#ef9e44', }, { id: 26, text: 'Anthony Robinson', color: '#45a5cc', }, { id: 27, text: 'Ellie Tomson', color: '#a067bd', }, { id: 28, text: 'Natalie Adams', color: '#3d44ec', }, { id: 29, text: 'Sofia Green', color: '#4ddcca', }]; const appointmentsText = [ 'Google AdWords Strategy', 'New Brochures', 'Brochure Design Review', 'Website Re-Design Plan', 'Rollout of New Website and Marketing Brochures', 'Update Sales Strategy Documents', 'Non-Compete Agreements', 'Approve Hiring of John Jeffers', 'Update NDA Agreement', 'Update Employee Files with New NDA', 'Submit Questions Regarding New NDA', 'Submit Signed NDA', 'Review Revenue Projections', 'Comment on Revenue Projections', 'Provide New Health Insurance Docs', 'Review Changes to Health Insurance Coverage', 'Review Training Course for any Ommissions', 'Recall Rebate Form', 'Create Report on Customer Feedback', 'Review Customer Feedback Report', 'Customer Feedback Report Analysis', 'Prepare Shipping Cost Analysis Report', 'Provide Feedback on Shippers', 'Select Preferred Shipper', 'Complete Shipper Selection Form', 'Upgrade Server Hardware', 'Upgrade Personal Computers', 'Upgrade Apps to Windows RT or stay with WinForms', 'Estimate Time Required to Touch-Enable Apps', 'Report on Tranistion to Touch-Based Apps', 'Submit New Website Design', 'Create Icons for Website', 'Create New Product Pages', 'Approve Website Launch', 'Update Customer Shipping Profiles', 'Create New Shipping Return Labels', 'Get Design for Shipping Return Labels', 'PSD needed for Shipping Return Labels', 'Contact ISP and Discuss Payment Options', 'Prepare Year-End Support Summary Report', 'Review New Training Material', 'Distribute Training Material to Support Staff', 'Training Material Distribution Schedule', 'Approval on Converting to New HDMI Specification', 'Create New Spike for Automation Server', 'Code Review - New Automation Server', 'Confirm Availability for Sales Meeting', 'Reschedule Sales Team Meeting', 'Send 2 Remotes for Giveaways', 'Discuss Product Giveaways with Management', 'Replace Desktops on the 3rd Floor', 'Update Database with New Leads', 'Mail New Leads for Follow Up', 'Send Territory Sales Breakdown', 'Territory Sales Breakdown Report', 'Report on the State of Engineering Dept', 'Staff Productivity Report', ]; function getRandomDuration(durationState: number) { const durationMin = Math.floor((durationState % 23) / 3 + 5) * 15; return durationMin * 60 * 1000; } function getRandomText(textIndex: number) { return appointmentsText[textIndex % appointmentsText.length]; } function filterAppointmentsByTime(appointments: string | any[], startDayHour: number, endDayHour: number) { const result: any[] = []; for (let i = 0; i < appointments.length; i += 1) { const { startDate } = appointments[i]; const { endDate } = appointments[i]; if (startDate.getDay() === endDate.getDay() && startDate.getHours() >= startDayHour - 1 && endDate.getHours() <= endDayHour - 1) { result.push(appointments[i]); } } return result; } export function generateAppointments(startDay: Date, endDay: Date, startDayHour: number, endDayHour: number) { const appointments: Appointment[] = []; let textIndex = 0; let durationState = 1; const durationIncrement = 19; for (let i = 0; i < resources.length; i += 1) { let startDate = startDay; while (startDate.getTime() < endDay.getTime()) { durationState += durationIncrement; const endDate = new Date(startDate.getTime() + getRandomDuration(durationState)); appointments.push({ text: getRandomText(textIndex), startDate, endDate, humanId: resources[i].id, }); textIndex += 1; durationState += durationIncrement; startDate = new Date(endDate.getTime() + getRandomDuration(durationState)); } } return filterAppointmentsByTime(appointments, startDayHour, endDayHour); }
window.exports = window.exports || {}; window.config = { transpiler: 'ts', typescriptOptions: { module: 'system', emitDecoratorMetadata: true, experimentalDecorators: true, jsx: 'react', }, meta: { 'react': { 'esModule': true, }, 'typescript': { 'exports': 'ts', }, 'devextreme/time_zone_utils.js': { 'esModule': true, }, 'devextreme/localization.js': { 'esModule': true, }, 'devextreme/viz/palette.js': { 'esModule': true, }, 'openai': { 'esModule': true, }, }, paths: { 'npm:': 'https://unpkg.com/', 'bundles:': '../../../../bundles/', 'externals:': '../../../../bundles/externals/', }, defaultExtension: 'js', map: { 'ts': 'npm:plugin-typescript@8.0.0/lib/plugin.js', 'typescript': 'npm:typescript@4.2.4/lib/typescript.js', 'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js', 'react': 'npm:react@17.0.2/umd/react.development.js', 'react-dom': 'npm:react-dom@17.0.2/umd/react-dom.development.js', 'prop-types': 'npm:prop-types/prop-types.js', 'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js', 'luxon': 'npm:luxon@3.4.4/build/global/luxon.min.js', 'es6-object-assign': 'npm:es6-object-assign', 'devextreme': 'npm:devextreme@link:../../packages/devextreme/artifacts/npm/devextreme/cjs', 'devextreme-react': 'npm:devextreme-react@link:../../packages/devextreme-react/npm/cjs', 'devextreme-quill': 'npm:devextreme-quill@1.7.1/dist/dx-quill.min.js', 'devexpress-diagram': 'npm:devexpress-diagram@2.2.5/dist/dx-diagram.js', 'devexpress-gantt': 'npm:devexpress-gantt@4.1.54/dist/dx-gantt.js', '@devextreme/runtime': 'npm:@devextreme/runtime@3.0.12', 'inferno': 'npm:inferno@7.4.11/dist/inferno.min.js', 'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js', 'inferno-create-element': 'npm:inferno-create-element@7.4.11/dist/inferno-create-element.min.js', 'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js', 'inferno-hydrate': 'npm:inferno-hydrate/dist/inferno-hydrate.min.js', 'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js', 'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js', 'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js', 'devextreme-cldr-data': 'npm:devextreme-cldr-data@1.0.3', // SystemJS plugins 'plugin-babel': 'npm:systemjs-plugin-babel@0.0.25/plugin-babel.js', 'systemjs-babel-build': 'npm:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js', // Prettier 'prettier/standalone': 'npm:prettier@2.8.8/standalone.js', 'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js', }, packages: { 'devextreme': { defaultExtension: 'js', }, 'devextreme-react': { main: 'index.js', }, 'devextreme/events/utils': { main: 'index', }, 'devextreme/localization/messages': { format: 'json', defaultExtension: 'json', }, 'devextreme/events': { main: 'index', }, 'es6-object-assign': { main: './index.js', defaultExtension: 'js', }, }, packageConfigPaths: [ 'npm:@devextreme/*/package.json', 'npm:@devextreme/runtime@3.0.12/inferno/package.json', ], babelOptions: { sourceMaps: false, stage0: true, react: true, }, }; System.config(window.config);
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.js'; ReactDOM.render(<App />, document.getElementById('scheduler'));
export const resources = [ { id: 0, text: 'David Carter', color: '#74d57b', }, { id: 1, text: 'Emma Lewis', color: '#1db2f5', }, { id: 2, text: 'Noah Hill', color: '#f5564a', }, { id: 3, text: 'William Bell', color: '#97c95c', }, { id: 4, text: 'Jane Jones', color: '#ffc720', }, { id: 5, text: 'Violet Young', color: '#eb3573', }, { id: 6, text: 'Samuel Perry', color: '#a63db8', }, { id: 7, text: 'Luther Murphy', color: '#ffaa66', }, { id: 8, text: 'Craig Morris', color: '#2dcdc4', }, { id: 9, text: 'Sandy Wood', color: '#c34cb9', }, { id: 10, text: 'Susan Bennett', color: '#3d44ec', }, { id: 11, text: 'Lilly Barnes', color: '#4ddcca', }, { id: 12, text: 'Marcus Price', color: '#2ec98d', }, { id: 13, text: 'David Stewart', color: '#3ff6ca', }, { id: 14, text: 'Joseph Smith', color: '#f665aa', }, { id: 15, text: 'Carter Wilson', color: '#d1c974', }, { id: 16, text: 'Wyatt Lopez', color: '#ff6741', }, { id: 17, text: 'John Long', color: '#ee53dc', }, { id: 18, text: 'Jack Rivera', color: '#795ac3', }, { id: 19, text: 'Victoria Adams', color: '#ff7d8a', }, { id: 20, text: 'Madison Anderson', color: '#4cd482', }, { id: 21, text: 'Luna Moore', color: '#9d67cc', }, { id: 22, text: 'Michael Bailey', color: '#5ab1ef', }, { id: 23, text: 'Jenny Powell', color: '#68e18f', }, { id: 24, text: 'Daniel Peterson', color: '#4dd155', }, { id: 25, text: 'Gabriel Gray', color: '#ef9e44', }, { id: 26, text: 'Anthony Robinson', color: '#45a5cc', }, { id: 27, text: 'Ellie Tomson', color: '#a067bd', }, { id: 28, text: 'Natalie Adams', color: '#3d44ec', }, { id: 29, text: 'Sofia Green', color: '#4ddcca', }, ]; const appointmentsText = [ 'Google AdWords Strategy', 'New Brochures', 'Brochure Design Review', 'Website Re-Design Plan', 'Rollout of New Website and Marketing Brochures', 'Update Sales Strategy Documents', 'Non-Compete Agreements', 'Approve Hiring of John Jeffers', 'Update NDA Agreement', 'Update Employee Files with New NDA', 'Submit Questions Regarding New NDA', 'Submit Signed NDA', 'Review Revenue Projections', 'Comment on Revenue Projections', 'Provide New Health Insurance Docs', 'Review Changes to Health Insurance Coverage', 'Review Training Course for any Ommissions', 'Recall Rebate Form', 'Create Report on Customer Feedback', 'Review Customer Feedback Report', 'Customer Feedback Report Analysis', 'Prepare Shipping Cost Analysis Report', 'Provide Feedback on Shippers', 'Select Preferred Shipper', 'Complete Shipper Selection Form', 'Upgrade Server Hardware', 'Upgrade Personal Computers', 'Upgrade Apps to Windows RT or stay with WinForms', 'Estimate Time Required to Touch-Enable Apps', 'Report on Tranistion to Touch-Based Apps', 'Submit New Website Design', 'Create Icons for Website', 'Create New Product Pages', 'Approve Website Launch', 'Update Customer Shipping Profiles', 'Create New Shipping Return Labels', 'Get Design for Shipping Return Labels', 'PSD needed for Shipping Return Labels', 'Contact ISP and Discuss Payment Options', 'Prepare Year-End Support Summary Report', 'Review New Training Material', 'Distribute Training Material to Support Staff', 'Training Material Distribution Schedule', 'Approval on Converting to New HDMI Specification', 'Create New Spike for Automation Server', 'Code Review - New Automation Server', 'Confirm Availability for Sales Meeting', 'Reschedule Sales Team Meeting', 'Send 2 Remotes for Giveaways', 'Discuss Product Giveaways with Management', 'Replace Desktops on the 3rd Floor', 'Update Database with New Leads', 'Mail New Leads for Follow Up', 'Send Territory Sales Breakdown', 'Territory Sales Breakdown Report', 'Report on the State of Engineering Dept', 'Staff Productivity Report', ]; function getRandomDuration(durationState) { const durationMin = Math.floor((durationState % 23) / 3 + 5) * 15; return durationMin * 60 * 1000; } function getRandomText(textIndex) { return appointmentsText[textIndex % appointmentsText.length]; } function filterAppointmentsByTime(appointments, startDayHour, endDayHour) { const result = []; for (let i = 0; i < appointments.length; i += 1) { const { startDate } = appointments[i]; const { endDate } = appointments[i]; if ( startDate.getDay() === endDate.getDay() && startDate.getHours() >= startDayHour - 1 && endDate.getHours() <= endDayHour - 1 ) { result.push(appointments[i]); } } return result; } export function generateAppointments(startDay, endDay, startDayHour, endDayHour) { const appointments = []; let textIndex = 0; let durationState = 1; const durationIncrement = 19; for (let i = 0; i < resources.length; i += 1) { let startDate = startDay; while (startDate.getTime() < endDay.getTime()) { durationState += durationIncrement; const endDate = new Date(startDate.getTime() + getRandomDuration(durationState)); appointments.push({ text: getRandomText(textIndex), startDate, endDate, humanId: resources[i].id, }); textIndex += 1; durationState += durationIncrement; startDate = new Date(endDate.getTime() + getRandomDuration(durationState)); } } return filterAppointmentsByTime(appointments, startDayHour, endDayHour); }
<!DOCTYPE html> <html lang="en"> <head> <title>DevExtreme Demo</title> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/24.2.3/css/dx.light.css" /> <link rel="stylesheet" type="text/css" href="styles.css" /> <script src="https://unpkg.com/core-js@2.6.12/client/shim.min.js"></script> <script src="https://unpkg.com/systemjs@0.21.3/dist/system.js"></script> <script type="text/javascript" src="config.js"></script> <script type="text/javascript"> System.import("./index.tsx"); </script> </head> <body class="dx-viewport"> <div class="demo-container"> <div id="scheduler"></div> </div> </body> </html>
#scheduler .dx-scheduler-cell-sizes-vertical { height: 100px; } #scheduler .dx-scheduler-cell-sizes-horizontal { width: 150px; }