Перейти к основному содержимому

React Gantt Overview

заметка

React Gantt is available under Commercial, Enterprise and Ultimate licenses. If you're using Individual or GPL editions of Gantt, please refer to the How to Start article for React.

Overview

DHTMLX Gantt is a pure JS component that can work in any browser environment. The Commercial and higher editions of Gantt include a React Gantt component that encapsulates DHTMLX Gantt and allows you to use it natively with React.

The wrapper lets you create a fully functional Gantt chart in your React applications using the familiar props/state model. Under the hood, it manages a standard DHTMLX Gantt instance, translating your React props (such as tasks and config) into the corresponding Gantt initialization and data structures.

Key features

  • Declarative data handling: Pass an array of tasks, links, resources, etc. as props.
  • Configurable: Map React props to the underlying gantt.config, gantt.templates, gantt.plugins, etc.
  • Access to the full Gantt API: Use a ref to call methods like getTask, updateTask, or addTaskLayer.
  • Easy customization: Use React components for templates, lightbox forms, or inline editors.

If you're new to DHTMLX Gantt, see the DHTMLX Gantt documentation for an overview of features like Work Time Calculation, Auto Scheduling, Resource Management, and more.

Installation and NPM Access

For up-to-date installation instructions for both the Evaluation and Professional builds, including npm registry configuration and offline examples, see the Installation Guide.

Once you have installed the package, you can import the wrapper in your React code as follows:

// Evaluation build (public npm)
import ReactGantt from '@dhtmlx/trial-react-gantt';
import '@dhtmlx/trial-react-gantt/dist/react-gantt.css';

// Professional build (private npm)
import ReactGantt from '@dhx/react-gantt';
import '@dhx/react-gantt/dist/react-gantt.css';

Version Requirements

  • React v18.0.0 or newer

Basic Usage

Here is a minimal snippet showing how to import and render the Gantt chart:

import { useState } from 'react';
import ReactGantt from '@dhx/react-gantt';
import '@dhx/react-gantt/dist/react-gantt.css';
import { demoData } from './DemoData'

export default function BasicGantt() {
const [theme, setTheme] = useState("terrace");
const [tasks, setTasks] = useState(demoData.tasks);
const [links, setLinks] = useState(demoData.links);

return (
<div style={ { height: '500px' }}>
<ReactGantt
tasks={tasks}
links={links}
theme={theme}
/>
</div>
);
}

Note that the above snippet shows how to include the commercial Gantt version. To use the trial code sources, include the package in the following way:

import ReactGantt from '@dhtmlx/trial-react-gantt';
import '@dhtmlx/trial-react-gantt/dist/react-gantt.css';

Where demoData has the following format:

export const demoData = {
tasks: [
{ id: 1, text: "Product Launch", type: "project", open: true, parent: 0},
{ id: 2, text: "Planning Phase", type: "project", open: true, parent: 1},
{ id: 3, text: "Requirement Gathering", type: "task", progress: 0.2,
start_date: "01-06-2025", duration: 3, parent: 2},
{ id: 4, text: "Technical Feasibility", type: "task", progress: 0.4,
start_date: "04-06-2025", duration: 2, parent: 2},
{ id: 5, text: "Implementation Phase", type: "project", progress: 0.1,
open: true, start_date: "08-06-2025", duration: 10, parent: 1},
{ id: 6, text: "Prototype Development", type: "task", progress: 0.0,
start_date: "08-06-2025", duration: 4, parent: 5},
{ id: 7, text: "Feature Testing", type: "task", progress: 0.0,
start_date: "12-06-2025", duration: 4, parent: 5},
{ id: 8, text: "Go-Live Milestone", type: "milestone", progress: 0,
start_date: "18-06-2025", duration: 0, parent: 1}
],
links: [
{ id: 1, source: 3, target: 4, type: "0" },
{ id: 2, source: 4, target: 5, type: "0" },
{ id: 3, source: 6, target: 7, type: "0" },
{ id: 4, source: 7, target: 8, type: "0" }
]
};
export {demoData};

Related article: dhtmlxReactGantt and Firebase Integration

Binding Data

The React Gantt wrapper offers flexible ways of loading and saving data. Conceptually, there are two primary approaches to manage changes in your Gantt data:

  • React (or a state manager) as the source of truth
  • Gantt as the source of truth

Either approach is valid, but you should pick one and follow it consistently to avoid unexpected behavior.

Looking for a deeper explanation?

This section gives a high-level overview of the two binding models. For a more detailed guide, including full examples, see Basics.

React (or a state manager) as the source of truth

In this pattern, ReactGantt receives all task/link data via props (from useState, Redux, Zustand, etc.). Whenever the user modifies tasks or links in the chart, Gantt calls the data.save callback. In that callback, you update your application state. When the state changes, React re-renders ReactGantt, and the Gantt instance is synchronized with the latest data.

import { useMemo, useState } from 'react';
import ReactGantt, { type Task, type Link } from '@dhtmlx/trial-react-gantt';
import '@dhtmlx/trial-react-gantt/dist/react-gantt.css';

export function MyGanttApp({ initialTasks, initialLinks }: {
initialTasks: Task[];
initialLinks: Link[];
}) {
const [tasks, setTasks] = useState<Task[]>(initialTasks);
const [links, setLinks] = useState<Link[]>(initialLinks);

const data = useMemo(
() => ({
save: (entity: string, action: string, item: any, id: string | number) => {
if (entity === 'task') {
setTasks((prev) => {
if (action === 'create') return [...prev, item as Task];
if (action === 'update') return prev.map((task) =>
task.id === id ? (item as Task) : task
);
if (action === 'delete') return prev.filter((task) => task.id !== id);
return prev;
});
}

if (entity === 'link') {
setLinks((prev) => {
if (action === 'create') return [...prev, item as Link];
if (action === 'update') return prev.map((link) =>
link.id === id ? (item as Link) : link
);
if (action === 'delete') return prev.filter((link) => link.id !== id);
return prev;
});
}
},
}),
[]
);

return (
<ReactGantt
tasks={tasks}
links={links}
data={data}
/>
);
}

This approach makes your React (or global) state the single source of truth. It works naturally with state managers such as Redux Toolkit, Zustand, MobX, Jotai, XState, or Valtio - you simply replace useState with your store hooks/selectors and move the update logic into the store.

For more examples (including integrations with specific managers) see React state as the source of truth.

Gantt as the source of truth

In this approach, Gantt itself keeps the authoritative copy of the data. You still initialize or load tasks and links (via props or URLs), but once the chart is running, Gantt handles changes internally and forwards updates to your backend or a custom handler, without going through React state on every edit.

import ReactGantt from '@dhtmlx/trial-react-gantt';
import '@dhtmlx/trial-react-gantt/dist/react-gantt.css';

export function GanttTransportExample() {
return (
<ReactGantt
data={{
load: '/api/gantt/data', // Gantt loads tasks/links from this endpoint
save: '/api/gantt/data', // Gantt sends changes back here
}}
/>
);
}

In this mode:

  • the local Gantt instance remains the primary holder of the current data
  • React doesn't re-render on every task/link change
  • bulk operations like Auto Scheduling are cheaper, because they don't trigger repeated React updates.

If you still keep some representation of tasks/links in React state, be careful not to overwrite Gantt's internal state with stale data.

For more details see Gantt as the source of truth.

Configuration & Props

The React wrapper accepts the config prop (mapped to gantt.config) and the templates prop (mapped to gantt.templates).

<ReactGantt
tasks={tasks}
links={links}
config= {{
scales: [
{ unit: "year", step: 1, format: "%Y" },
{ unit: "month", step: 1, format: "%F, %Y" },
{ unit: "day", step: 1, format: "%d %M" },
],
columns: [
{ name: "text", tree: true, width: "*", resize: true },
{ name: "start_date", align: "center", resize: true },
{ name: "duration", align: "center", resize: true },
{
name: "custom",
align: "center",
template: (task) => <AlertButton task={task} onClick={handleButtonClick} />,
resize: true,
},
{ name: "add", width: 44 },
],
}}
templates= {{
task_text: (start, end, task) => `#${task.id}: ${task.text}`,
task_class: (start, end, task) => {
return task.priority === 'high' ? 'highlight-task' : '';
},
}}
/>

Using React components in templates

When specifying templates in props, you can return React elements from your template functions:

function PriorityBadge({ priority }) {
return <span style={{color: 'red' }}>{priority}</span>;
}

<ReactGantt
templates={{
task_text: (start, end, task) => {
return <PriorityBadge priority={task.priority} />;
}
}}
/>
заметка

Internally, DHTMLX Gantt manipulates the DOM in a non-React way. When you return React components from templates, they are embedded into Gantt's HTML via portals. Keep in mind that for large datasets, heavily rendering complex React components may impact performance.

You can override many aspects using templates:

You can find the full list of props supported by React Gantt in the following article: Configuration

Themes & Styling

Gantt is shipped with several built-in themes that can be activated via the theme prop and changed dynamically:

import { useEffect, useRef } from 'react';
import ReactGantt from "@dhx/react-gantt";
import "@dhx/react-gantt/dist/react-gantt.css";

export default function BasicInitDemo() {
const [theme, setTheme] = useState("terrace");
const tasks = [...];
const links = [...];

const switchTheme = () => {
setTheme((prevTheme) => (prevTheme === "terrace" ? "dark" : "terrace"));
};


return (
<div style={{height: '600px'}}>
<div>
<button onClick={switchTheme}>Switch Theme</button>
</div>
<ReactGantt
tasks={tasks}
links={links}
theme={theme} /*!*/
/>
</div>
);
}

You can find detailed descriptions of the existing themes in this article.

Themes can be additionally customized using custom styles and by overriding CSS variables:

:root {
--dhx-gantt-task-background: #d96c49;
--dhx-gantt-task-color: #fff;
--dhx-gantt-task-border-radius: 8px;
}

For additional configuration, please check the Skins Customization guide.

Replacing the Lightbox

DHTMLX Gantt comes with a built-in configurable task editor called Lightbox.

If needed, you can replace it with a React-based modal or any other component in one of the following ways:

By providing a custom component via the customLightbox prop

To do so, pass a component through the customLightbox prop:

import React, { useState } from 'react';

export interface CustomLightboxProps {
data: any;
onSave: (task: any) => void;
onCancel: () => void;
onDelete: () => void;
}

const CustomLightbox: React.FC<CustomLightboxProps> = ({
data,
onSave,
onCancel,
onDelete
}) => {
const [description, setDescription] = useState<string>(data.text || '');

const handleSaveClick = () => {
onSave({ ...data, text: description });
};

const modalStyles = {
...
};

return (
<div>
<div style={modalStyles.overlay} onClick={onCancel} />
<div style={modalStyles.content}>
<h3>Edit Task</h3>
<div>
<label>Description:</label>
<input
type="text"
value={description}
onChange={(e) =/> setDescription(e.target.value)}
style={{width: '100%', padding: '8px', marginTop: '10px' }}
/>
</div>
<div style={modalStyles.buttonGroup}>
<button onClick={handleSaveClick}>Save</button>
<button onClick={onCancel}>Cancel</button>
<button onClick={onDelete}>Delete</button>
</div>
</div>
</div>
);
};

export default CustomLightbox;

After that, you can use the added component in the following way:

import { useEffect, useRef } from 'react';
import ReactGantt from "@dhx/react-gantt";
import "@dhx/react-gantt/dist/react-gantt.css";
import CustomLightbox from "./EditorModal";

export default function BasicInitDemo() {
const ganttRef = useRef(null);

const tasks = [...];
const links = [...];

useEffect(() => {
//const gantt = ganttRef.current?.instance;

}, []);

return (
<ReactGantt
ref={ganttRef}
tasks={tasks}
links={links}
customLightbox={<CustomLightbox />} />
);
}

By using onBeforeLightbox event prop

For more complex scenarios, you can capture the onBeforeLightbox event (fired when the Lightbox is invoked) and override the default behavior:

import { useEffect, useRef } from 'react';
import ReactGantt from "@dhx/react-gantt";
import "@dhx/react-gantt/dist/react-gantt.css";
import { useNavigate } from 'react-router-dom';


export default function BasicInitDemo() {
const ganttRef = useRef<any>(null);

const tasks = [...];
const links = [...];
const navigate = useNavigate();

const handleTaskEdit = (id: any) => {
const ganttInstance = ganttRef.current?.instance;
navigate(`/editor/${id}`, { state: { task: ganttInstance.getTask(id) } });
};

return (
<ReactGantt
ref={ganttRef}
tasks={tasks}
links={links}
onBeforeLightbox={handleTaskEdit} />
);
}

By using JS Gantt API

Please refer to Custom Lightbox for further details on overriding or extending the built-in Lightbox.

Replacing built-in Modals

The default UI includes two modal popups:

  • the confirm dialog that appears before deleting a task
  • the confirm dialog that appears before deleting a link

Both can be overridden using the modals prop of ReactGantt:

<ReactGantt
...
modals={{
onBeforeTaskDelete: ({
task,
callback,
ganttInstance,
}: {
task: Task;
callback: () => void;
ganttInstance: GanttStatic;
}) => void,
onBeforeLinkDelete: ({
link,
callback,
ganttInstance,
}: {
link: Link;
callback: () => void;
ganttInstance: GanttStatic;
}) => void,
}}
...
/>

You can use these props to activate your custom modals whenever a confirmation dialog is called by Gantt. Calling the callback() provided in the arguments will finalize the deletion of the appropriate task or link. To cancel the deletion, simply close the modal without calling the callback.

Using React Components in Grid

In headers

The label property of a grid column can be either a string or a ReactElement. This lets you embed React-based filters, buttons, or other UI directly in the column header:

const config: GanttConfig = {
columns: [
{ name: "text", label: "Name", tree: true, width: 180,
resize: true },
// Embedding React element directly
{ name: "start_date", label: <DateFilter />, width: 150,
align: "center", resize: true },
// Alternatively, using a function returning a React element:
{ name: "end_date", label: () => <DateFilter />, width: 150,
align: "center", resize: true },
...
],
row_height: 40,
grid_width: 550,
};

When the wrapper detects a React element in a label or any other template property, it will render this element using a React Portal in the grid's header cell.

In cells

Grid cells are defined by the template property of the column. This template function receives a task object and must return either a plain string or a ReactElement:

import { useRef } from 'react';

function AlertButton({ task, onClick }) {
return <button onClick={onClick}>{`Task ID: ${task.id}`}</button>;
}

export default function GanttWithGridCells({ handleButtonClick, ganttRef }) {
const config = {
columns: [
{ name: "text", tree: true, width: 180, resize: true },
{ name: "start_date", width: 150, align: "center", resize: true },
{ name: "duration", width: 80, align: "center", resize: true },
{
name: "custom",
align: "center",
label: <span>My Column</span>,
width: 140,
// Returning a React element
template: (task) => (
<AlertButton
task={task}
onClick={() => {
handleButtonClick(task);
// Force re-rendering of the task if needed
ganttRef.current?.instance.updateTask(task.id);
}}
/>
),
resize: true,
},
{ name: "add", width: 44 },
],
row_height: 40,
grid_width: 550,
};

return <ReactGantt ref={ganttRef} config={config} /* ...other props */ />;
}

By returning a React element from your column template, you can create fully interactive content (buttons, dropdowns, badges, etc.) in each cell of the Gantt grid. Internally, the wrapper will inject those elements via portals into the DOM nodes that Gantt manages.

In inline editors

DHTMLX Gantt supports inline editing for grid cells. In this React wrapper, you can provide your own custom React editors by specifying an editor object in the column config, and then mapping an editor name to a React component in the inlineEditors prop. Check the example below.

Define a React-based inline editor component:

import React, {
useState,
forwardRef,
useImperativeHandle
} from 'react';
import { InlineEditorMethods, InlineEditorProps } from '@dhx/react-gantt';


const MyInlineEditor = forwardRef<InlineEditorMethods, InlineEditorProps>(
({ initialValue, task, save, cancel, ganttInstance }, ref) => {
const [value, setValue] = useState(initialValue || "");

useImperativeHandle(ref, (): InlineEditorMethods => ({
getValue: () => value,
setValue: (val: any) => setValue(val),
isValid: () => true,
focus: () => {

},
isChanged: (originalValue: any) => {
return originalValue !== value;
},

save: () => { }
}));

return (
<input
type="text"
value={value}
onChange={e =/> setValue(e.target.value)}
autoFocus
/>
);
}
);

export default MyInlineEditor;

Use the custom editor in your Gantt configuration:

import ReactGantt from "@dhx/react-gantt";
import MyInlineEditor from "./CustomInlineEditor";

function Demo() {
const config = {
columns: [
{ name: "text", tree: true, width: 180, resize: true },
{
name: "duration",
width: 80,
align: "center",
editor: { type: "customInputEditor", map_to: "text" }, /*!*/
resize: true
},
{ name: "start_date", width: 150 },
{ name: "add", width: 44 }
]
};

return (
<ReactGantt
config={config}
inlineEditors={{
customInputEditor: MyInlineEditor /*!*/
} }
tasks={[/*...*/]}
links={[/*...*/]}
/>
);
}

When the user double-clicks the column cell, Gantt will display your editor component in place. The wrapper's internal code calls the methods (getValue, setValue, etc.) that you expose via useImperativeHandle(ref, ...), ensuring the Gantt instance stays in sync with the changes in your component.

The value of type of the editor object must match the key in inlineEditors.

The map_to property specifies the property of the Task object from which the editor will read and write values. Please refer to the article that covers inline editing for further details.

If you're implementing an editor that makes something more complex than writing a value to a property of a task - you need to implement a required logic in the save function and specify the map_to option of the input to "auto". In this case, the gantt won't modify the task object, but instead will call the save function when it's time to apply the changes made to the editor. The initialValue of the editor will be passed as null.

заметка

Note, you can define non-React inline editors using the editor_types property of the config property.

Editor component properties

  • initialValue - (any) - the initial value of the editor
  • task - (Task) - the task that is being edited
  • save - (function) - tells the gantt to save and close the editor
  • cancel - (function) - tells the gantt to close the editor without saving
  • ganttInstance - (GanttStatic) - the current instance of the underlying Gantt object

Filtering

Use the filter prop to specify a filter for the tasks that should be displayed:

const [filter, setFilter] = useState<((task: Task) => boolean) | null>(null);

function showCompleted() {
setFilter(() => (task: Task) => task.progress === 1);
}
function resetFilter() {
setFilter(null);
}

return (
<ReactGantt
...
filter={filter}
...
/>
);

To filter resources in the Resource Panel, use the resourceFilter prop:

function handleResourceSelectChange(resourceId: string | null) {
setSelectedResource(resourceId);
if (resourceId === null) {
setResourceFilter(null);
} else {
setResourceFilter(
() => (resource: ResourceItem) => String(resource.id) === String(resourceId)
);
}
}

return (
<ReactGantt
ref={ganttRef}
tasks={tasks}
links={links}
resources={resources}
resourceFilter={resourceFilter}
config={config}
templates={templates}
plugins={{auto_scheduling: true }}
/>
);

Working Calendars

To enable work-time calculations in ReactGantt, make sure to enable work_time:

  const config: GanttConfig = {
...
work_time: true
};

Working calendars can be passed to ReactGantt through the calendars prop:

const calendars: Calendar[] = [
{
id: "global",
hours: ["8:00-12:00", "13:00-17:00"], // global work hours for weekdays
days: {
weekdays: {
0: false, // 0 = Sunday, 6 = Saturday
1: true,
2: true,
3: true,
4: true,
5: true,
6: false
},
dates: {
"2025-04-06": true, // override work hours for a specific date
"2025-04-08": false
}
}
}
];

return (
<div style={{height: '100%', display: 'flex', flexDirection: 'column' }}>
<ReactGantt
...
calendars={calendars}
...
/>
</div>
);

In order to highlight working time in the Gantt Timeline or to perform work-time calculations, you can use the provided useWorkTime hook:

import ReactGantt, { useWorkTime, Calendar } from "@dhx/react-gantt";

export default function GanttTemplatesDemo() {
const ganttRef = useRef<ReactGanttRef>(null);

const { isWorkTime } = useWorkTime(ganttRef);
const templates: GanttTemplates = {
timeline_cell_class: (task: Task, date: Date) => {
return isWorkTime({ date, task }) ? "" : "weekend";
}
};

const calendars: Calendar[] = [
{
id: "global",
hours: ["8:00-12:00", "13:00-17:00"], // global work hours for weekdays
days: {
weekdays: {
0: false, // 0 = Sunday, 6 = Saturday
1: true,
2: true,
3: true,
4: true,
5: true,
6: false
},
dates: {
"2025-04-06": true, // override work hours for a specific date
"2025-04-08": false
}
}
}
];

return (
<div style={{height: '100%', display: 'flex', flexDirection: 'column' }}>
<ReactGantt
...
calendars={calendars}
templates={templates}
config={config}
ref={ganttRef}
/>
</div>
);
};

Alternatively, you can access the inner Gantt object and use working time methods directly.

Grouping Tasks

Use the groupTasks prop to group tasks by any of task's properties:

  const [grouping, setGrouping] = useState<GroupConfig | boolean>({
relation_property: 'status',
groups:[
{id: 1, name: "New"},
{id: 2, name: "In Progress"},
{id: 3, name: "Done"}
],
group_id: "key",
group_text: "label"
});

return (
<ReactGantt
ref={ganttRef}
tasks={tasks}
links={links}
groupTasks={grouping}
/>
);

To disable grouping, set groupTasks to false:

setGrouping(false);

Vertical Markers in Timeline Area

Vertical markers can be added to ReactGantt via the markers property:

  const projectStartMarker = {
id: "marker1",
start_date: new Date(2025, 3, 2),
text: "Project start!",
css: "project-start"
};
const projectEndMarker = {
id: "marker2",
start_date: new Date(2025, 3, 16),
text: "Project end",
css: "project-end"
};

const [markers, setMarkers] = useState<Marker[]>([
projectStartMarker,
projectEndMarker
]);

return (
<div style={{height: '100%', display: 'flex', flexDirection: 'column' }}>
<ReactGantt
...
markers={markers}
...
/>
</div>
);
заметка

Note, the text property of the Marker object accepts either HTML string or React Element

Accessing the Underlying Gantt API

In most cases, ReactGantt props are enough to configure your chart. However, sometimes you'll need direct access to the DHTMLX Gantt API for advanced operations (e.g. worktime calculations, gantt.showDate, gantt.unselectTask, or custom zooming).

Using built-in hooks

ReactGantt provides ready to use hooks that expose some methods of Gantt API. Please check the related article Configuration.

Using a Ref

For the cases when declarative props and built-in hooks are not enough, the wrapper allows accessing the internal Gantt instance using ref:

import React, { useRef, useEffect } from 'react';
import ReactGantt, { ReactGanttRef } from '@dhx/react-gantt';

export function DirectRefExample({ tasks, links }) {
const ganttRef = useRef<ReactGanttRef>(null);

useEffect(() => {
const gantt = ganttRef.current?.instance;
if (!gantt) return;

// here you can call ANY Gantt API method
console.log('All tasks:', gantt.getTaskByTime());
gantt.showDate(new Date());
}, []);

return (
<ReactGantt
ref={ganttRef}
tasks={tasks}
links={links}
/>
);
}

See the DHTMLX Gantt API Reference for the full list of methods.

Avoid conflicts with React props

  • If you manually call gantt.parse({ tasks, links }) or gantt.addTask() from your code, be aware you may need to keep the React props in sync. Otherwise, the next time React re-renders, it may overwrite your manual changes.
  • The recommended approach is to rely on the wrapper's props for tasks and links, or manage them in your React state. Then let the wrapper handle re-parsing.

Compatibility with SSR Frameworks (Next.js, Remix)

заметка

Starting from ReactGantt v9.0.12 the wrapper is SSR-ready. You can import it in Next.js or Remix without turning SSR off. If you use older versions - you must disable or delay server-side rendering for any route or component that uses ReactGantt.

заметка

During the server rendering, the component outputs only a placeholder <div>, the actual Gantt markup is created during the browser-side hydration phase.

Next.js

ReactGantt is SSR-compatible, but your own component must be a client component in most real-world scenarios.

You will need to add "use client" at the top of your component whenever you use a ref to access the Gantt instance, pass event handlers or callbacks, return React elements from templates,

This is a typical configuration:

'use client';

import "@dhx/react-gantt/dist/react-gantt.css";
import ReactGantt from '@dhx/react-gantt';

export default function GanttPage() {
return (
<div style={{ height: '100vh' }}>
<ReactGantt tasks={/* ... */} links={/* ... */} />
</div>
);
}

If you use legacy versions (v9.0.11 or older), you need to dynamically import your ReactGantt component with SSR disabled:

import dynamic from 'next/dynamic';

const GanttDemo = dynamic(() => import('../components/GanttDemo'), {
ssr: false
});

export default function GanttPage() {
return (
<div style={{ height: '100vh' }}>
<GanttDemo />
</div>
);
}

Remix

Starting from v9.0.12, no <ClientOnly> wrapper is required:


import "@dhx/react-gantt/dist/react-gantt.css";
import ReactGantt from '@dhx/react-gantt';

export default function GanttPage() {
return (
<div style={{ height: '100vh' }}>
<ReactGantt tasks={/* ... */} links={/* ... */} />
</div>
);
}

If you use legacy versions (v9.0.11 or older), you have to conditionally render the Gantt component only on the client:

import { ClientOnly } from 'remix-utils/client-only';
import ReactGantt from '@dhx/react-gantt';

export default function GanttPage() {

return (
<div style={{height: '100vh' }}>
<ClientOnly fallback={<p>Loading...</p>}>
{() => <ReactGantt
tasks={/* ... */}
links={/* ... */}
/>}
</ClientOnly>
</div>
);
}

Next Steps