How to use gantt in react app

how to use gantt in react app @Dzmitry

Hello webix_B_123,

The common requirements for complex widgets to work is:

the webix variable should be available in the global context (it should be available before the complex widget’s sources are included or imported).

The Webix libriary/complex widgets can be included either:

  1. via the script tag on some page (e.g., index.html ):
<script src="../node_modules/@xbs/webix-pro/webix.js" type="text/javascript"></script>

or

  1. via importing the required dependencies. The second approach requires for the webix variable to be defined in the global scope.
    For example:
import * as webix from "@xbs/webix-pro";
window.webix = webix;

Here you can find instuctions about React with Webix: GitHub - webix-hub/react-demo-complex: This demo shows how to init Webix complex widgets in React components. .

THanks for your reply, no issue with webix global variable, issue with gantt component, I’m not able to use gantt even after importing gantt js file from nodemodules

THis is the error I have got, >> cannot read properties of undefined (reading ā€˜$protoWait’).

Not able to use any of your webix complex widgets in our react app, I did find any docs to interate these complez widgets like gantt, please help @intregal

Hello webix_B_123

I tries to reproduce your issue using Gantt component in react app but everuthing works fine.
Here is the code of my gantt component:

import React, { Component } from "react";

import "@xbs/webix-pro/webix.css";
import "@xbs/gantt/codebase/gantt.css";

class GanttView extends Component {
  constructor(props) {
    super(props);
    this.uiContainer = React.createRef();
  }

  render() {
    return <div ref={this.uiContainer} style={{ height: "100%" }}></div>;
  }

  componentDidMount() {
    const container = this.uiContainer.current;

    webix.ready(() => {
      require("@xbs/gantt");

      this.ui = webix.ui({
        view: "gantt",
        url: "https://docs.webix.com/gantt-backend",
        container,
      });
    });

    this.resObserver = new ResizeObserver(() => {
      if (this.ui) this.ui.adjust();
    });
    this.resObserver.observe(container);
  }

  componentWillUnmount() {
    if (this.ui) {
      this.ui.destructor();
      this.ui = null;
    }
    this.resObserver.disconnect();
  }

  shouldComponentUpdate() {
    // as component is not linked to the in-app data model, there is no need in updates
    return false;
  }
}

export default GanttView;

If you still have the issue, could you clarify and give this information:

  1. webix and gantt versions
    Also for a more readable error log, it is better to use the debug version during development.
  2. the version of react
  3. how do you import gantt and webix?
    To use gantt component you should import it the same way: react-demo-complex/src/FilesView.js at main Ā· webix-hub/react-demo-complex Ā· GitHub
    Also additional information about importing complex widgets you can read here.
  4. did you apply any customizations to Gantt?

Thank you very much for your response. I’ve been eagerly waiting to integrate Webix Gantt into my React application.

I’m using the latest versions of Webix and Gantt — specifically version 11.0.3.

I haven’t applied any customizations. I’m importing Gantt directly using:

import "@xbs/gantt/codebase/gantt.js";

My project is based on Create React App with TypeScript, and I’m using React 19 with functional components only — no class components. Could you please provide an example that uses functional components?

Additionally, I’ve encountered the same error not just with Gantt, but also when trying to integrate other complex widgets like the Query Builder.

Also, I had a quick question regarding Gantt — is it possible to implement zoom in / zoom out functionality in Webix Gantt?

Lastly, please let me know if any changes are required in configuration files such as webconfig.js, webpack.config.js, or tsconfig.json.

I’d really appreciate your help in resolving this issue.

Hello webix_B_123

Here is the code with React functional component:

import React, { useEffect, useRef } from "react";

import "@xbs/webix-pro/webix.css";
import "@xbs/gantt/codebase/gantt.css";

function GanttView(props) {
  const uiGantt = useRef(null);
  const uiContainer = useRef(null);

  useEffect(() => {
    const resObserver = new ResizeObserver(() => {
      if (uiGantt.current) uiGantt.current.adjust();
    });

    const container = uiContainer.current;

    webix.ready(() => {
      require("@xbs/gantt");

      uiGantt.current = webix.ui({
        view: "gantt",
        url: "https://docs.webix.com/gantt-backend",
        container,
      });
    });

    resObserver.observe(container);

    return () => {
      if (uiGantt.current) {
        uiGantt.current.destructor();
        uiGantt.current = null;
      }
      resObserver.disconnect();
    };
  }, []);

  return <div ref={uiContainer} style={{ height: "100%" }}></div>;
}

export default GanttView;

Try to apply the code provided.

As for zoom in / zoom out Webix Gantt has zoom functionality for scales.
Please check the example: Code Snippet

Thanks a lot @Natalia_Shilova for your quick reply, I really appriciate, let me try and come back.

Gone through this sinppet, is there ZOOM IN and ZOOM OUT options using mouse wheel?

Webix Gantt does not support zooming in and out using the mouse wheel. The available zoom functionality is limited to predefined scale options, which allow you to adjust the time scale through specific settings.

Thanks we will use that then.

Hi @Natalia_Shilova, thanks — the solution works great.

I have a few follow-up questions regarding the Gantt chart:

  1. Focus on Today’s Date:
    Is it possible to automatically focus the Gantt view on the current date (today), so that:
  • The chart scrolls to today on load
  • The left side (past) acts like history
  • The right side emphasizes upcoming or future records?
  1. Handling Infinite End Dates:
    Some records don’t have an end date. What’s the best way to display these?
  • Can we handle open-ended or infinite tasks in the Gantt?
  • For such tasks, I’d prefer the label not to be centered, but instead aligned outward or flagged visually to indicate ā€œno endā€.
  1. Export Options:
    What export features are supported for the Gantt chart? (e.g., PDF, PNG, Excel?)
  2. Filtering & Search:
    Is there a way to add filtering or a search box to help locate tasks quickly within the Gantt?

I know this is quite a bit — appreciate your help and guidance on these!

Thanks again,

@Dzmitry @Natalia_Shilova any solution for above queries?

Hi @webix_B_123 ,

Apologies for the delay. Here are some explanations and examples to your questions. Most of them require additional customizations of Gantt, but definitely feasible:

  1. Focus on Today’s Date:
    Is it possible to automatically focus the Gantt view on the current date (today), so that:
  • The chart scrolls to today on load

Currently, there is no built-in solution, but it is possible to calculate the scroll position based on the scale and the desired date.
Here’s an example where scroll is set after the init: Code Snippet

  • The left side (past) acts like history
  • The right side emphasizes upcoming or future records?

If you need to highlight past dates (color them in grey, for example), it is possible to extend the holiday highlighting and add custom conditions to apply styling: Code Snippet

  1. Handling Infinite End Dates:
    Some records don’t have an end date. What’s the best way to display these?
  • Can we handle open-ended or infinite tasks in the Gantt?
  • For such tasks, I’d prefer the label not to be centered, but instead aligned outward or flagged visually to indicate ā€œno endā€.

Unfortunately, Gantt does not have a built-in support for infinite events. Each project and task should have start/end dates to be present on a timeline.
An event can have an extremely large duration that is set on the backend when the data is requesteds (for example, a few years from the current date).
On the other hand, there is a «milestone» type of event that does not have a duration - just a start date. In theory, it is possible to extend the current rendering logic to present certain milestones differently (by expanding their width to the end of the scale and adding a custom text label in the desired position).

  1. Export Options:
    What export features are supported for the Gantt chart? (e.g., PDF, PNG, Excel?)
  2. Filtering & Search:
    Is there a way to add filtering or a search box to help locate tasks quickly within the Gantt?

Here’s an example of both filtering options and export: Code Snippet

Gantt supports standard export methods of Webix

Filtering data by multiple fields is certainly possible via API, but the filter(s) should be placed outside of the Gantt as a separate UI part and/or put into table headers.

The above snippet shows two independent filters with different behavior:

  • ā€œFind taskā€ highlights occurrences and scrolls to nearest match. This implementation is based on our guide for text search and highlighting. To implement a custom template with highlights on a Gantt, I’ve created a custom version of Gantt’s tree (TreeView)
  • ā€œFilter by typeā€ is simpler and allows to select projects, tasks, and milestones.
    Please be aware that filtering in tree-like structures shows not only the found item, but its parents (always) and sub-items, if the found item is a branch. You can configure filtering with filterMode setting of the Tasks collection:
// should be called after init, but before calling any tasks.filter()
const gantt = $$("gantt");
const tasks = gantt.getService("local").tasks();
   tasks.define({
      filterMode: {
      showSubItems: false,
  }
});

Hope these examples are helpful. If you have any further questions, we will be glad to assist.

@Listopad Thanks for detailed explanation. I will go through the solutions and get back to you.

Hi @Listopad, thanks again for the detailed explanation and examples — they were really helpful!

I understand now that the links are intentionally hidden because some may not have related tasks at the expected coordinates. However, in our case, we do want to keep the links visible.

Could you please guide us on the best way to override this filtering logic or safely enable all links, even if they don’t match the default coordinate assumptions?

or is there a way to keep valid links and hide invalid?

Thanks again for your support!

// hide links: some won't have related tasks on expected coordinates
links.filter((link) => {
  return false;
});

@Natalia_Shilova @Listopad @Dzmitry Also how can I format or add html styled labels inside bars?

Hello webix_B_123,

To keep valid links and hide invalid you should update filtered links while filtering (the row 109):

links.data.callEvent("onStoreUpdated", [link.id, link, "update"]);

As for:

formatting or adding html styled labels inside bars

you should customize the ā€œchart/barsā€ grid and override the BarsTemplate(obj, _) default method(row 53) which is responsible for the bars template. In this method you can change the html of the bars depending on your needs and return a string.

Please check the example: Code Snippet

@Natalia_Shilova Thanks for the quick reply, I will check and get back if anything required.

1 Like

@Listopad @Natalia_Shilova @Dzmitry
https://snippet.webix.com/hwsnfbqm

Right now, it highlights the matching text when I type in the search box and scrolls to the first match. But it does not filter the tasks — all tasks are still shown.

I want to filter the tasks based on the search input, so that only matching tasks are visible, and still keep the highlighting as shown in the example.

How can I modify the example to do both: filter the records and highlight the matches?


I’m using a custom API to load data into Webix Gantt, based on this custom backend service (GanttBackendService.ts). Everything is working fine when loading data initially. Here’s what I’m trying to achieve and clarify:

1. Refreshing Gantt without Recreating

In my current setup, I’m using webix.ui() inside a useEffect hook to initialize the Gantt chart.
Question:
How can I refresh the Gantt chart data (from my API) without completely destroying and recreating the Gantt instance?

2. Calling Custom Save API on Add/Edit/Delete

I want to call a custom API when the user adds, updates, or deletes a task. This action is triggered from a drawer form on click of ā€œSaveā€/ā€œDoneā€.
Question:
Where is the best place to call this API? Should I override addTask, updateTask, and removeTask in my custom backend?
If yes, how do I pass the updated task data from the form?

3. Suppressing Auto API Calls (e.g., /undefinedtasks/3/position)

Currently, when I drag and drop a task, Gantt is making a request to a default URL like this:
http://localhost:3000/undefinedtasks/3/position
Question:
How can I stop this default call and instead use my custom API?

4. Other Auto Calls – Did I Miss Anything?

Besides CRUD and drag-drop position updates, are there any other auto API calls that I should be aware of or override to fully control backend communication?

5. Customizing Tooltips

Is there a way to customize or override the tooltips shown on Gantt tasks?

6. Hiding Fields in Edit Drawer

In the edit drawer (task editor), I want to hide some default fields like progress, planned dates, etc.
Question:
Is there a clean way to remove or hide these fields?

Here’s a brief overview of my setup:

  • Gantt is rendered inside a React component using webix.ui()
  • Backend is customized using gantt.services.Backend
  • Task and link data are loaded via a custom API
  • Drawer is used for editing tasks
GanttBackendService.ts

import * as webix from '@xbs/webix-pro';
import {
  GanttTask,
  GanttInstance,
  GanttData,
  GanttLink,
} from '../types/gantt.types';

export function getMdrStandardsTimelinesData(
  gantt: GanttInstance,
  ganttData: GanttData
) {
  return class MdrStandardsTimelinesData extends gantt.services.Backend {
    private gantt: GanttInstance | null;

    constructor() {
      super();
      this.gantt = null;
    }

    init(gantt: GanttInstance): void {
      this.gantt = gantt;
    }

    tasks(): Promise<GanttTask[]> {
      return Promise.resolve(ganttData?.tasks || []);
    }

    links(): Promise<GanttLink[]> {
      return Promise.resolve(ganttData?.links || []);
    }

    // dummpy operations handlers
    addTask() {
      return webix.promise.resolve({ id: webix.uid() });
    }

    updateTask() {
      return webix.promise.resolve({ id: webix.uid() });
    }

    removeTask() {
      return webix.promise.resolve({ id: webix.uid() });
    }

    addLink() {
      return webix.promise.resolve({ id: webix.uid() });
    }

    updateLink() {
      return webix.promise.resolve({ id: webix.uid() });
    }

    removeLink() {
      return webix.promise.resolve({ id: webix.uid() });
    }
  };
}

import React, { JSX, memo, useEffect, useRef } from 'react';
import * as webix from '@xbs/webix-pro';
import { useAppSettings } from '@context/AppSettingsContext';
import { loadGanttData } from './helpers/loadData';
import { GanttTask, GanttData } from './types/gantt.types';
import { getMdrStandardsTimelinesData } from './services/GanttBackendService';
import { createCustomTree } from './services/CustomTreeView';
import { cellWidths, defaultScale, scalesMap } from './scales/scales';
import { scrollGanttToToday } from './services/ScrollGanttToToday';
import { FilterState } from './filters/FilterManager';

declare global {
  interface Window {
    webix: typeof webix;
  }
}

window.webix = webix;

interface TimelinesGanttProps {
  filterState?: FilterState;
}

interface WebixTreeTable {
  filter: (filterFn?: ((item: GanttTask) => boolean) | string) => void;
}

interface WebixGantt extends webix.ui.gantt {
  clearAll: () => void;
  parse: (data: GanttData) => void;
  queryView: (view: string) => WebixTreeTable;
  config: webix.ui.gantt['config'] & {
    //add custom configs types here
  };
}

const TimelinesGantt: React.FC<TimelinesGanttProps> = (): JSX.Element => {
  const uiGantt = useRef<WebixGantt | null>(null);
  const uiContainer = useRef<HTMLDivElement | null>(null);
  const appSettings = useAppSettings();
  const corpId = appSettings.appSettings.corpId;

  useEffect(() => {
    let isComponentMounted = true;
    const resObserver = new ResizeObserver(() => {
      if (uiGantt.current) uiGantt.current.adjust();
    });

    const container = uiContainer.current;
    if (!container) {
      return undefined;
    }

    // Cleanup any existing instance first
    if (uiGantt.current) {
      uiGantt.current.destructor();
      uiGantt.current = null;
    }

    // Only initialize if not already initialized
    if (!uiGantt.current) {
      webix.ready(() => {
        if (!isComponentMounted) return;

        try {
          // Load gantt component
          const gantt = require('@xbs/gantt');

          // Load data first
          loadGanttData()
            .then((ganttData: GanttData) => {
              if (!isComponentMounted) return;

              if (
                !ganttData ||
                !ganttData.tasks ||
                !Array.isArray(ganttData.tasks)
              ) {
                console.error('Invalid Gantt data structure:', ganttData);
                throw new Error('Invalid Gantt data structure');
              }

              const Backend = gantt.services.Backend;
              const Tree = gantt.views.tree;
              const overrideMap = new Map();

              overrideMap.set(
                Backend,
                getMdrStandardsTimelinesData(gantt, ganttData)
              );

              overrideMap.set(
                Tree,
                createCustomTree(gantt, {
                  _showStudyCount: false,
                  readonly: false,
                })
              );

              //scroll to today
              scrollGanttToToday(gantt, overrideMap);

              // Create Gantt instance with the loaded data
              uiGantt.current = webix.ui({
                id: 'mdr-standards-timelines-webix-gantt',
                view: 'gantt',
                container,
                baseline: true,
                borderless: true,
                compact: false,
                split: true,
                readonly: false,
                links: true,
                override: overrideMap,
                treeWidth: 325,
                markers: [
                  {
                    text: 'Now',
                    css: 'webix_gantt_today_marker',
                    now: true,
                  },
                ],
                scales: scalesMap[defaultScale],
                scaleCellWidth: cellWidths[defaultScale],
                css: 'gantt-container',
                scroll: 'xy',
                height: window.innerHeight - 200,
                on: {
                  onInit: function (app: WebixGantt['$app']) {
                    const backend = this.getService('backend');

                    if (backend) {
                      backend.init(this);
                    }

                    focusOnTodayInGantt(app);
                  },
                  onViewShow: function () {
                    const backend = this.getService('backend');

                    if (backend) {
                      backend.init(this);
                    }
                  },
                },
              }) as WebixGantt;
            })
            .catch((error: Error) => {
              if (isComponentMounted) {
                console.error('Error loading or parsing Gantt data:', error);
              }
            });
        } catch (error) {
          if (isComponentMounted) {
            console.error('Failed to load Gantt component:', error);
          }
        }
      });
    }

    resObserver.observe(container);

    return () => {
      isComponentMounted = false;
      if (uiGantt.current) {
        uiGantt.current.destructor();
        uiGantt.current = null;
      }
      resObserver.disconnect();
    };
  }, [corpId]);

  return (
    <div className="gantt-chart-container" ref={uiContainer}>
      {/* Gantt chart will be initialized here */}
    </div>
  );
};

const focusOnTodayInGantt = (gantt: WebixGantt['$app']): void => {
  const tasks = gantt.getService('local').tasks();
  const today = new Date();

  tasks.waitData.then(() => {
    gantt.callEvent('chart:scroll', [today]);
  });
};

export default memo(TimelinesGantt);

export type { WebixGantt, WebixTreeTable };
export { focusOnTodayInGantt };