diff --git a/docs/integrations/react/configuration-props.md b/docs/integrations/react/configuration-props.md index 30f4f3f7..924c4bae 100644 --- a/docs/integrations/react/configuration-props.md +++ b/docs/integrations/react/configuration-props.md @@ -180,135 +180,30 @@ The `@dhx/react-gantt` library is designed to be as declarative as possible for In these cases, you can use two additional approaches to tap into the underlying DHTMLX Gantt functionality: -- **React hooks** specifically provided by the wrapper to bridge Gantt's data stores and scheduling logic +- **[React hooks](integrations/react/hooks.md)** specifically provided by the wrapper to bridge Gantt's data stores and scheduling logic -- **Direct access** to the Gantt instance via a `\ref` if the built-in hooks don't cover all your needs +- **Direct access** to the Gantt instance via a ref if the built-in hooks don't cover all your needs -### Using built-in hooks +### Using built-in hooks -The `@dhx/react-gantt` library exposes a set of optional hooks that connect React components to internal Gantt APIs. These hooks provide a 'bridge' to Gantt's underlying methods and data stores. You can either call these hooks directly in your components or compose them into your own custom hooks for specialized features like resource histograms. +The `@dhx/react-gantt` library provides hooks for event subscriptions, resource management, datastore access, undo/redo, zoom, selection, and work time calculations. -#### useGanttDatastore<T>(ganttRef, storeName) +See the dedicated **[Hooks](integrations/react/hooks.md)** page for the complete reference, including: -The `useGanttDatastore` hook hives a read-only access to a specific Gantt datastore. -The common use is accessing the resource datastore, baseline, or any other built-in or custom store. - -It provides the following functions: - -- `getItem(id)` - returns a specified item from the datastore - -- `getItems()` - returns all items in the specified datastore - -- `hasChild(id: string | number)` - checks if an item has children - -- `getChildren(id: string | number)` - retrieves child items - -~~~js -import { useMemo } from 'react'; -import { useGanttDatastore } from '@dhx/react-gantt'; - -function MyResourceList({ ganttRef }) { - const resourceStore = useGanttDatastore(ganttRef, 'resource'); - - const resourceIds = resourceStore.getItems().map(item => item.id); - - // for demonstration, just log the data - useMemo(() => { - console.log('Resource IDs:', resourceIds); - }, [resourceIds]); - - return null; -} -~~~ - -You can use this hook whenever you need direct low-level data from a specific datastore. For example, checking if a resource is a group vs. an individual. - -#### useResourceAssignments(ganttRef) - -The `useResourceAssignments` hook exposes Gantt's resource-related methods, such as retrieving assignments for a resource or enumerating which resources are assigned to a given task. - -It provides the following functions: - -- `getResourceAssignments(resourceId, taskId?)` - bridge to [](api/method/getresourceassignments.md) -- `getTaskResources(taskId)` - bridge to [](api/method/gettaskresources.md) - -~~~js -import React from 'react'; -import { useResourceAssignments } from '@dhx/react-gantt'; - -export function ResourceUsage({ ganttRef, taskId }) { - const { getTaskResources } = useResourceAssignments(ganttRef); - - const resources = getTaskResources(taskId); - return ( -
- Task {taskId} assigned to: - {resources.map(r => r.text).join(', ')} -
- ); -} -~~~ - -You may need this hook for any custom logic around resource usage, e.g., calculating allocated hours or grouping tasks by owner - -#### useWorkTime(ganttRef) - -Provides a direct bridge for built-in DHTMLX Gantt worktime functions, such as [](api/method/isworktime.md), [](api/method/calculateenddate.md), [](api/method/calculateduration.md). - -You'll need this hook for highlighting working/non-working time according to Gantt work calendar settings, as well as for date operations in accordance to work calendars. - -It provides the following functions: - -- `isWorkTime({ date:Date, unit?: string, task?:Task })` - bridge to [](api/method/isworktime.md) -- `calculateEndDate({start:Date, duration:number, unit?: string, task?: Task})` - bridge to [](api/method/calculateenddate.md) -- `calculateDuration({start:Date, end:Date, task?: Task})` - bridge to [](api/method/calculateduration.md) -- `getClosestWorkTime({ date:Date, unit?: string, task?: Task, dir?: "past"|"future" })` - bridge to [](api/method/getclosestworktime.md) - - -~~~js -import { useEffect, useRef, useState } from 'react'; -import ReactGantt, {GanttTemplates, useWorkTime} from "@dhx/react-gantt"; -import "@dhx/react-gantt/dist/react-gantt.css"; - -export default function GanttTemplatesDemo() { - const ganttRef = useRef(null); - - const { isWorkTime }= useWorkTime(ganttRef); - ... - const templates: GanttTemplates = { - timeline_cell_class: (task: Task, date: Date) => { - return isWorkTime({date, task}) ? "" : "weekend"; - } - }; - ... -~~~ - -#### Composing hooks into your own custom hooks - -A great practice is to build your own domain or project-specific hooks using these fundamental bridging hooks. For instance, if you want to create a resource histogram, you might create a custom hook that caches capacity values, sums resource usage, etc.: - -~~~js -import { useMemo } from 'react'; -import { useGanttDatastore, useResourceAssignments } from '@dhx/react-gantt'; - -export function useResourceHistogram(ganttRef) { - const resourceStore = useGanttDatastore(ganttRef, 'resource'); - const { getResourceAssignments } = useResourceAssignments(ganttRef); - - // Custom logic: capacity caching, group detection, etc. - // ... - return { - // e.g. getCapacity, getAllocatedValue - }; -} -~~~ +- [useGanttEvent](integrations/react/hooks.md#useganttEvent) — event subscriptions with lifecycle management +- [useResourceAssignments](integrations/react/hooks.md#useresourceassignments) — resource assignment queries and mutations +- [useGanttDatastore](integrations/react/hooks.md#useganttdatastore) — read-only datastore access +- [useUndoRedo](integrations/react/hooks.md#useundoredo) — undo/redo state and actions +- [useZoom](integrations/react/hooks.md#usezoom) — zoom control and state +- [useSelection](integrations/react/hooks.md#useselection) — task selection tracking +- [useWorkTime](integrations/react/hooks.md#useworktime) — work time calculations ### Direct access to Gantt instance with ref -While these hooks handle most advanced needs, you might still want direct access to the entire Gantt instance. For that, the ref approach remains available: +While hooks handle most advanced needs, you might still want direct access to the entire Gantt instance. For that, the ref approach remains available: -~~~jsx -import React, { useRef, useEffect } from 'react'; +~~~tsx +import { useRef, useEffect } from 'react'; import ReactGantt, { ReactGanttRef } from '@dhx/react-gantt'; export function DirectRefExample({ tasks, links }) { @@ -317,26 +212,16 @@ export function DirectRefExample({ tasks, links }) { 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 ( - - ); + return ; } ~~~ :::note -info Be mindful that if you alter tasks/links in the direct Gantt instance while also feeding them as React props, -you should keep them in sync or re-parse the data. Otherwise, the next React re-rendering may overwrite your manual changes. +If you alter tasks or links via the direct Gantt instance while also feeding them as React props, keep them in sync. Otherwise, the next React re-render may overwrite your manual changes. ::: -If you want to do something not exposed by a prop, you can still call gantt methods directly. See [Accessing the Underlying Gantt API](integrations/react/overview.md#accessingtheunderlyingganttapi) for more details. +See [Accessing the Underlying Gantt API](integrations/react/overview.md#accessingtheunderlyingganttapi) for more details. diff --git a/docs/integrations/react/hooks.md b/docs/integrations/react/hooks.md new file mode 100644 index 00000000..dce2a017 --- /dev/null +++ b/docs/integrations/react/hooks.md @@ -0,0 +1,378 @@ +--- +title: "React Gantt Hooks" +sidebar_label: "Hooks" +description: "Built-in React hooks for DHTMLX Gantt — event subscriptions, resource assignments, undo/redo, zoom, selection, datastores, and work time calculations." +--- + +# React Gantt Hooks + +The `@dhx/react-gantt` wrapper provides a set of React hooks that bridge your components to Gantt's internal APIs without requiring direct access to the Gantt instance. These hooks handle lifecycle management automatically — subscriptions are created on mount and cleaned up on unmount. + +All hooks accept a `ganttRef` parameter — a React ref to the `ReactGantt` component: + +~~~tsx +import { useRef } from 'react'; +import ReactGantt, { ReactGanttRef } from '@dhx/react-gantt'; + +function MyGanttApp() { + const ganttRef = useRef(null); + + // pass ganttRef to any hook + return ; +} +~~~ + +## useGanttEvent + +Subscribe to a Gantt event with automatic lifecycle management. Attaches the handler on mount and detaches on unmount. + +~~~ts +import { useGanttEvent } from '@dhx/react-gantt'; +~~~ + +**Signature:** +~~~ts +useGanttEvent(ganttRef, eventName, handler, options?) +~~~ + +**Parameters:** + +- `ganttRef` — ref to the ReactGantt component +- `eventName` — the Gantt event name (e.g., `'onAfterTaskUpdate'`, `'onStoreUpdated'`) +- `handler` — callback function +- `options.target` — *(optional)* accessor function that resolves the event source from the Gantt instance. When omitted, events attach to the Gantt instance itself. + +Works with any object that implements `attachEvent`/`detachEvent` — the Gantt instance, extensions, datastores, and UI modules: + +~~~tsx +import { useGanttEvent } from '@dhx/react-gantt'; + +function MyComponent({ ganttRef }) { + // Gantt instance events (default target) + useGanttEvent(ganttRef, 'onAfterTaskUpdate', (id, task) => { + console.log('Task updated:', id); + }); + + // Extension events — pass a target accessor + useGanttEvent(ganttRef, 'onAfterZoom', (level) => { + console.log('Zoomed to level:', level); + }, { target: (gantt) => gantt.ext.zoom }); + + // Datastore events + useGanttEvent(ganttRef, 'onStoreUpdated', () => { + console.log('Resource store changed'); + }, { target: (gantt) => gantt.getDatastore('resource') }); + + return null; +} +~~~ + +If the event or target is not available (e.g., a plugin is not enabled), the hook silently does nothing. + +## useResourceAssignments + +Access Gantt's resource assignment and resource management methods. + +~~~ts +import { useResourceAssignments } from '@dhx/react-gantt'; +~~~ + +**Returns:** + +- `getResourceAssignments(resourceId, taskId?)` — bridge to [](api/method/getresourceassignments.md). Returns assignments for a resource, optionally filtered by task. +- `getTaskResources(taskId)` — bridge to [](api/method/gettaskresources.md). Returns unique resources assigned to a task. +- `getTaskAssignments(taskId)` — bridge to [](api/method/gettaskassignments.md). Returns all assignments for a task. +- `getAllResources()` — returns all items from the resource datastore. +- `setTaskAssignments(taskId, assignments)` — replaces all assignments for a task. Each assignment should have `resource_id` and optionally `value`. + +~~~tsx +import { useResourceAssignments } from '@dhx/react-gantt'; + +function ResourcePanel({ ganttRef, taskId }) { + const { getTaskAssignments, getAllResources, setTaskAssignments } = + useResourceAssignments(ganttRef); + + const assignments = getTaskAssignments(taskId); + const resources = getAllResources(); + + function reassign() { + setTaskAssignments(taskId, [ + { resource_id: 1, value: 8 }, + { resource_id: 3, value: 4 } + ]); + } + + return ( +
+

Assigned: {assignments.map(a => a.resource_id).join(', ')}

+

Available: {resources.map(r => r.text).join(', ')}

+ +
+ ); +} +~~~ + +## useGanttDatastore + +Read-only access to any Gantt datastore — tasks, links, resources, assignments, baselines, or custom stores. + +~~~ts +import { useGanttDatastore } from '@dhx/react-gantt'; +~~~ + +**Signature:** +~~~ts +const store = useGanttDatastore(ganttRef, storeName) +~~~ + +**Returns:** + +- `getItem(id)` — returns a single item by ID +- `getItems()` — returns all items +- `hasChild(id)` — checks if an item has children (tree datastores) +- `getChildren(id)` — returns child IDs (tree datastores) +- `eachItem(callback)` — iterates through all items in the datastore +- `find(filter)` — returns items matching a predicate function +- `count()` — returns total number of items +- `exists(id)` — checks if an item exists + +~~~tsx +import { useGanttDatastore, ResourceItem } from '@dhx/react-gantt'; + +function ResourceList({ ganttRef }) { + const store = useGanttDatastore(ganttRef, 'resource'); + + // Find all leaf (non-group) resources + const individuals = store.find(r => !store.hasChild(r.id)); + + // Iterate all resources + store.eachItem(resource => { + console.log(resource.text, store.hasChild(resource.id) ? '(group)' : ''); + }); + + return ( +
    + {individuals.map(r =>
  • {r.text}
  • )} +
+ ); +} +~~~ + +## useUndoRedo + +Track undo/redo stack state and provide actions. Automatically subscribes to task and link change events to keep state current. + +~~~ts +import { useUndoRedo } from '@dhx/react-gantt'; +~~~ + +**Returns:** + +- `canUndo` — `boolean`, true when the undo stack is non-empty +- `canRedo` — `boolean`, true when the redo stack is non-empty +- `undo()` — performs an undo operation +- `redo()` — performs a redo operation + +Returns disabled state (`canUndo: false, canRedo: false`) if the undo plugin is not enabled. + +~~~tsx +import { useUndoRedo } from '@dhx/react-gantt'; + +function UndoRedoButtons({ ganttRef }) { + const { canUndo, canRedo, undo, redo } = useUndoRedo(ganttRef); + + return ( +
+ + +
+ ); +} +~~~ + +:::note +The undo plugin must be enabled in the `plugins` prop for this hook to work: +~~~tsx + +~~~ +::: + +## useZoom + +Manage timeline zoom levels and track the current zoom state. Initializes the zoom extension automatically. + +~~~ts +import { useZoom } from '@dhx/react-gantt'; +~~~ + +**Signature:** +~~~ts +const zoom = useZoom(ganttRef, levels?) +~~~ + +**Parameters:** + +- `ganttRef` — ref to the ReactGantt component +- `levels` — *(optional)* array of zoom level configurations. Defaults to 5 built-in levels: Day, Week, Month, Quarter, Year. + +**Returns:** + +- `currentLevel` — name of the active zoom level +- `levels` — the zoom level configurations +- `zoomIn()` — zoom to a more detailed level +- `zoomOut()` — zoom to a less detailed level +- `setLevel(name)` — jump to a specific zoom level by name + +~~~tsx +import { useZoom } from '@dhx/react-gantt'; + +function ZoomControls({ ganttRef }) { + const { currentLevel, levels, zoomIn, zoomOut, setLevel } = useZoom(ganttRef); + + return ( +
+ + + +
+ ); +} +~~~ + +You can provide custom zoom levels: + +~~~tsx +const customLevels = [ + { + name: 'sprint', + label: 'Sprint (2 weeks)', + scale_height: 60, + min_column_width: 70, + scales: [ + { unit: 'month', step: 1, format: '%F %Y' }, + { unit: 'week', step: 2, format: 'Sprint %W' }, + ], + }, + // ...more levels +]; + +const zoom = useZoom(ganttRef, customLevels); +~~~ + +## useSelection + +Track which task is currently selected in the Gantt chart. + +~~~ts +import { useSelection } from '@dhx/react-gantt'; +~~~ + +**Returns:** + +- `selectedId` — the ID of the currently selected task, or `null` if nothing is selected + +~~~tsx +import { useSelection } from '@dhx/react-gantt'; + +function TaskDetails({ ganttRef }) { + const { selectedId } = useSelection(ganttRef); + + if (!selectedId) return

Select a task to see details.

; + + return

Selected task: {selectedId}

; +} +~~~ + +## useWorkTime + +Bridge to Gantt's built-in work time calculation functions. Use this for highlighting non-working time and for date arithmetic that respects work calendars. + +~~~ts +import { useWorkTime } from '@dhx/react-gantt'; +~~~ + +**Returns:** + +- `isWorkTime({ date, unit?, task? })` — bridge to [](api/method/isworktime.md) +- `calculateEndDate({ start, duration, unit?, task? })` — bridge to [](api/method/calculateenddate.md) +- `calculateDuration({ start, end, task? })` — bridge to [](api/method/calculateduration.md) +- `getClosestWorkTime({ date, unit, task?, dir? })` — bridge to [](api/method/getclosestworktime.md) + +~~~tsx +import ReactGantt, { GanttTemplates, ReactGanttRef, useWorkTime } from '@dhx/react-gantt'; + +function GanttWithWeekends() { + const ganttRef = useRef(null); + const { isWorkTime } = useWorkTime(ganttRef); + + const templates: GanttTemplates = { + timeline_cell_class: (task, date) => { + return isWorkTime({ date, task }) ? '' : 'weekend'; + } + }; + + return ; +} +~~~ + +## Composing hooks + +Build domain-specific hooks by composing the built-in hooks. For example, a resource histogram hook: + +~~~ts +import { useMemo, useCallback } from 'react'; +import { useGanttDatastore, useResourceAssignments, useWorkTime } from '@dhx/react-gantt'; + +export function useResourceHistogram(ganttRef) { + const resourceStore = useGanttDatastore(ganttRef, 'resource'); + const { getResourceAssignments } = useResourceAssignments(ganttRef); + const { isWorkTime } = useWorkTime(ganttRef); + + const isGroupResource = useCallback((resource) => { + return resourceStore.hasChild(resource.id); + }, [resourceStore]); + + const getAllocatedValue = useCallback((tasks, resource) => { + return tasks.reduce((sum, task) => { + const assignments = getResourceAssignments(resource.id, task.id); + return sum + assignments.reduce((acc, a) => acc + Number(a.value), 0); + }, 0); + }, [getResourceAssignments]); + + const getCapacity = useCallback((date, resource) => { + if (isGroupResource(resource)) return -1; + return isWorkTime({ date }) ? 8 : 0; + }, [isGroupResource, isWorkTime]); + + return { getAllocatedValue, getCapacity, isGroupResource }; +} +~~~ + +## Direct access to Gantt instance + +While hooks handle most advanced needs, you can still access the entire Gantt instance directly via a ref: + +~~~tsx +import { useRef, useEffect } from 'react'; +import ReactGantt, { ReactGanttRef } from '@dhx/react-gantt'; + +function DirectRefExample({ tasks, links }) { + const ganttRef = useRef(null); + + useEffect(() => { + const gantt = ganttRef.current?.instance; + if (!gantt) return; + gantt.showDate(new Date()); + }, []); + + return ; +} +~~~ + +:::note +If you alter tasks or links via the direct Gantt instance while also feeding them as React props, keep them in sync. Otherwise, the next React re-render may overwrite your manual changes. +::: diff --git a/docs/integrations/react/overview.md b/docs/integrations/react/overview.md index 93fbff69..92e66b6b 100644 --- a/docs/integrations/react/overview.md +++ b/docs/integrations/react/overview.md @@ -918,7 +918,7 @@ In most cases, ReactGantt props are enough to configure your chart. However, som ### Using built-in hooks -ReactGantt provides ready to use hooks that expose some methods of Gantt API. Please check the related article [](integrations/react/configuration-props.md). +ReactGantt provides hooks for event subscriptions, resource management, undo/redo, zoom, selection, datastore access, and work time calculations. See the **[Hooks reference](integrations/react/hooks.md)** for the complete API. ### Using a Ref diff --git a/sidebars.js b/sidebars.js index 4d892a1f..4210fd21 100644 --- a/sidebars.js +++ b/sidebars.js @@ -67,6 +67,7 @@ module.exports = { "integrations/react/installation", "integrations/react/quick-start", "integrations/react/configuration-props", + "integrations/react/hooks", // Data & State Management {