{"id":15403,"date":"2026-04-10T11:08:37","date_gmt":"2026-04-10T08:08:37","guid":{"rendered":"https:\/\/dhtmlx.com\/blog\/?p=15403"},"modified":"2026-04-10T12:03:12","modified_gmt":"2026-04-10T09:03:12","slug":"using-xstate-in-react-gantt-and-scheduler-apps-for-complex-state-scenarios","status":"publish","type":"post","link":"https:\/\/dhtmlx.com\/blog\/using-xstate-in-react-gantt-and-scheduler-apps-for-complex-state-scenarios\/","title":{"rendered":"Using XState in React Gantt and Scheduler Apps for Complex State Scenarios"},"content":{"rendered":"<p>When building a project planning application in React with components like a Gantt chart, state management quickly becomes a complex problem: the basic approach does not scale well for managing state transitions and keeping the UI in sync.<\/p>\n<p>Using DHTMLX React Gantt and React Scheduler, we already discovered what can be accomplished with <a href=\"https:\/\/dhtmlx.com\/blog\/using-dhtmlx-gantt-chart-for-react-with-redux-toolkit-principles-benefits-and-implementation-tips\/\" rel=\"noopener\" target=\"_blank\">Redux Toolkit<\/a>, <a href=\"https:\/\/dhtmlx.com\/blog\/using-zustand-state-management-apps-dhtmlx-react-gantt-scheduler\/\" rel=\"noopener\" target=\"_blank\">Zustand<\/a>, and <a href=\"https:\/\/dhtmlx.com\/blog\/managing-state-dhtmlx-react-gantt-chart-scheduler-mobx\/\" rel=\"noopener\" target=\"_blank\">MobX<\/a>. Each option has its own strengths, but they all follow a somewhat similar mindset.<\/p>\n<p>Today, we take a new direction and explore XState, which follows a noticeably different, machine-based approach that is particularly well-suited for managing complex logic in React Gantt applications.<\/p>\n<p>By the end of this blog post, you\u2019ll get a clear idea of how to implement XState in a React Gantt-based project planning app.<\/p>\n<h2>What is XState and How It Works<\/h2>\n<p><a href=\"https:\/\/stately.ai\/docs\/xstate\" rel=\"noopener\" target=\"_blank\">XState<\/a> is a comprehensive tool for handling non-trivial state management and orchestration tasks in web apps written in pure JavaScript or popular front-end frameworks such as React. It\u2019s not as commonly used as something like Redux or Zustand, but it has been around for quite a while and serves to meet more complex state management requirements.<\/p>\n<p>XState is built around the idea of finite state machines (FSM). At first sight, this time-tested mathematical concept may seem vague, but it makes much more sense when applied in practice. Instead of letting the state change freely, FSM allows you to define a set of possible states and describe how the app data can move from one well-defined state to another. The crucial point is that these transitions are explicit. The image below clearly illustrates the core ideas behind state machines in XState.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/dhtmlx.com\/blog\/wp-content\/uploads\/2026\/04\/bandicam-2026-04-10-11-31-24-871.png\" alt=\"illustration of a state machine concept\" width=\"828\" height=\"318\" class=\"alignleft size-full wp-image-15421\" srcset=\"https:\/\/dhtmlx.com\/blog\/wp-content\/uploads\/2026\/04\/bandicam-2026-04-10-11-31-24-871.png 828w, https:\/\/dhtmlx.com\/blog\/wp-content\/uploads\/2026\/04\/bandicam-2026-04-10-11-31-24-871-300x115.png 300w, https:\/\/dhtmlx.com\/blog\/wp-content\/uploads\/2026\/04\/bandicam-2026-04-10-11-31-24-871-768x295.png 768w\" sizes=\"auto, (max-width: 828px) 100vw, 828px\" \/><br \/>\n<em>Source: XState<\/em><\/p>\n<p>Changes between states are triggered by events, which are most commonly related to user interactions with the UI. State transitions in XState are accompanied by the execution of actions that update data stored in what is called the context. When things get complicated, XState adds another layer called statecharts. This extension helps organize complex state logic into a visual structure, including such aspects as hierarchies, concurrency, and communication.<\/p>\n<p>Once a single state machine is not enough, XState introduces actors. These are multiple machines that can run in parallel and communicate with each other. In more complex apps, this kind of separation can actually make things easier.<\/p>\n<p>What all of this gives you is a more structured way to handle application behavior. Instead of reacting to changes on the fly, you define upfront what is allowed to happen and what is not. It helps exclude invalid states from the workflow (like two conflicting states at the same time) and simplify debugging.<\/p>\n<p>There\u2019s also a broader ecosystem around XState known as the Stately platform. Designed by the creator of XState and his team, this platform provides a robust package of tools to enhance user experience and productivity with XState. For instance, it includes Stately Studio (visual editor), Stately Sky (cloud deployment), as well as numerous developer and inspection tools.<\/p>\n<p>You can get acquainted with other XState concepts on the official website of the project. We turn to the question of practical differences from other libraries and use-case scenarios for XState.<\/p>\n<h2>When XState is a Viable Option<\/h2>\n<p>XState is often mentioned as a powerful option, but in everyday React work, it\u2019s not usually the first tool developers reach for. More familiar choices like Zustand, Redux Toolkit, or MobX tend to feel easier to adopt and quicker to apply. There are fairly practical reasons for that. XState comes with a different mental model, and it often takes some time to get used to it. It is also criticized for verbosity and overengineering, especially in simpler scenarios. <\/p>\n<p>Here is the comparison table that gives a clear picture of how XState differs from popular state management libraries that we\u2019ve encountered earlier:<\/p>\n<table>\n<tbody>\n<tr>\n<td><strong>Criteria<\/strong><\/td>\n<td><strong>XState<\/strong><\/td>\n<td><strong>Redux Toolkit\/Zustand\/MobX<\/strong><\/td>\n<\/tr>\n<tr>\n<td>Primary focus<\/td>\n<td>Modeling app behavior via events and transitions<\/td>\n<td>Storing data and updating it when something changes<\/td>\n<\/tr>\n<tr>\n<td>How state updates are performed<\/td>\n<td>Events are sent to the machine, which determines how the state changes (i.e., indirectly)<\/td>\n<td>Direct state updates via functions, reducers, or mutations<\/td>\n<\/tr>\n<tr>\n<td>Handling complex workflows<\/td>\n<td>Built-in support for hierarchical and parallel states, coordinated updates, and asynchronous orchestration<\/td>\n<td>Sequential updates via reducers, setters, or reactivity; rely on custom logic for coordination<\/td>\n<\/tr>\n<tr>\n<td>Boilerplate<\/td>\n<td>Requires more setup and structure from the start<\/td>\n<td>Usually quicker to set up<\/td>\n<\/tr>\n<tr>\n<td>Learning curve<\/td>\n<td>Steep<\/td>\n<td>From low to moderate<\/td>\n<\/tr>\n<tr>\n<td>Tooling<\/td>\n<td>Powerful tooling ecosystem fueled by the Stately platform<\/td>\n<td>DevTools and middleware support<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Analyzing the above, XState feels less like a general-purpose solution and more like a niche (or specialized) tool. Instead of focusing on storing and updating data, it\u2019s really about describing how things are allowed to behave and change over time. That approach doesn\u2019t clash with React, but it can feel like too much for simpler use cases. <\/p>\n<p>In practice, XState often ensures predictable state management in React apps, where complex UI behavior is involved, and different processes need to stay in sync. Project management apps built with <a href=\"https:\/\/dhtmlx.com\/docs\/products\/dhtmlxGantt-for-React\/\" rel=\"noopener\" target=\"_blank\">DHTMLX React Gantt<\/a> and <a href=\"https:\/\/dhtmlx.com\/docs\/products\/dhtmlxScheduler-for-React\/\" rel=\"noopener\" target=\"_blank\">React Scheduler<\/a> often fall into this category, as they involve complex interactions, asynchronous data flows, and coordinated state changes.<\/p>\n<p>At this point, it probably makes more sense to stop talking about concepts and actually see how this works in code. We\u2019re not going to dive too deep into the XState architecture here, but try to give a general idea of how it behaves in a real setup. <\/p>\n<p>To do that, we\u2019ll outline the main steps required to integrate XState with DHTMLX React Gantt. The same pattern applies to our React Scheduler component as well, so focusing on one is enough to understand the overall approach.<\/p>\n<h2>Key Steps for Integrating DHTMLX React Gantt with XState<\/h2>\n<p>Although the overall integration flow remains consistent across different state management solutions, each library brings in its own peculiarities. Here, we\u2019ll focus on the steps that are specifically related to how to manage state in DHTMLX React Gantt with XState.<\/p>\n<p>If you plan to implement a similar scenario in your web project, our documentation includes the <a href=\"https:\/\/docs.dhtmlx.com\/gantt\/integrations\/react\/state\/xstate\/\" rel=\"noopener\" target=\"_blank\">DHTMLX React Gantt + XState tutorial<\/a> as well as the same <a href=\"https:\/\/docs.dhtmlx.com\/scheduler\/integrations\/react\/state\/xstate\/\" rel=\"noopener\" target=\"_blank\">integration guide for DHTMLX React Scheduler<\/a>. Also, there is a working <a href=\"https:\/\/github.com\/dhtmlx\/react-gantt-xstate-starter\" rel=\"noopener\" target=\"_blank\">GitHub project for Gantt<\/a> based on the tutorial. <\/p>\n<h3>Setting Up the State Machine Structure<\/h3>\n<p>We start with the state machine, the core element of the state management layer in XState. First, you describe the state machine structure (or what it will contain) via TypeScript interfaces:<\/p>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\"><span class=\"kw5\">import<\/span> <span class=\"br0\">&#123;<\/span> createMachine<span class=\"sy0\">,<\/span> assign <span class=\"br0\">&#125;<\/span> from <span class=\"st0\">'xstate'<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\n<span class=\"kw5\">import<\/span> type <span class=\"br0\">&#123;<\/span> Link<span class=\"sy0\">,<\/span> GanttConfig<span class=\"sy0\">,<\/span> SerializedTask <span class=\"br0\">&#125;<\/span> from <span class=\"st0\">'@dhtmlx\/trial-react-gantt'<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\n<span class=\"kw5\">import<\/span> <span class=\"br0\">&#123;<\/span> seedTasks<span class=\"sy0\">,<\/span> seedLinks<span class=\"sy0\">,<\/span> defaultZoomLevels<span class=\"sy0\">,<\/span> type ZoomLevel <span class=\"br0\">&#125;<\/span> from <span class=\"st0\">'.\/seed\/Seed'<\/span><span class=\"sy0\">;<\/span><br \/>\n<br \/>\n<span class=\"kw5\">export<\/span> <span class=\"kw5\">interface<\/span> Snapshot <span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; tasks<span class=\"sy0\">:<\/span> SerializedTask<span class=\"br0\">&#91;<\/span><span class=\"br0\">&#93;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\n&nbsp; links<span class=\"sy0\">:<\/span> Link<span class=\"br0\">&#91;<\/span><span class=\"br0\">&#93;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\n&nbsp; config<span class=\"sy0\">:<\/span> GanttConfig<span class=\"sy0\">;<\/span> &nbsp;<br \/>\n<span class=\"br0\">&#125;<\/span><br \/>\n<br \/>\n<span class=\"kw5\">export<\/span> <span class=\"kw5\">interface<\/span> ContextType <span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; tasks<span class=\"sy0\">:<\/span> SerializedTask<span class=\"br0\">&#91;<\/span><span class=\"br0\">&#93;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\n&nbsp; links<span class=\"sy0\">:<\/span> Link<span class=\"br0\">&#91;<\/span><span class=\"br0\">&#93;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\n&nbsp; config<span class=\"sy0\">:<\/span> GanttConfig<span class=\"sy0\">;<\/span><br \/>\n<br \/>\n&nbsp; past<span class=\"sy0\">:<\/span> Snapshot<span class=\"br0\">&#91;<\/span><span class=\"br0\">&#93;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\n&nbsp; future<span class=\"sy0\">:<\/span> Snapshot<span class=\"br0\">&#91;<\/span><span class=\"br0\">&#93;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\n&nbsp; maxHistory<span class=\"sy0\">:<\/span> number<span class=\"sy0\">;<\/span> &nbsp;<br \/>\n<span class=\"br0\">&#125;<\/span><\/div><\/div>\n<p>As you can see, the <strong>ContextType<\/strong> interface includes the machine\u2019s full context (internal data store), including Gantt data and history tracking, while the <strong>Snapshot<\/strong> interface is required to control the state via the undo\/redo feature. <\/p>\n<h3>Defining Events and Actions<\/h3>\n<p>The next step is to specify events that operate as signals for state changes in the Gantt chart, and the actions executed by the state machine in response to these events. <\/p>\n<p>In our Gantt scenario, all user interactions (task editing, updating links, changing zoom, or using undo\/redo operations) that lead to changes in the state structure are represented as discrete events:<\/p>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\">type SetZoomEvent <span class=\"sy0\">=<\/span> <span class=\"br0\">&#123;<\/span> type<span class=\"sy0\">:<\/span> <span class=\"st0\">'SET_ZOOM'<\/span><span class=\"sy0\">;<\/span> level<span class=\"sy0\">:<\/span> ZoomLevel <span class=\"br0\">&#125;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\ntype UndoEvent <span class=\"sy0\">=<\/span> <span class=\"br0\">&#123;<\/span> type<span class=\"sy0\">:<\/span> <span class=\"st0\">'UNDO'<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\ntype RedoEvent <span class=\"sy0\">=<\/span> <span class=\"br0\">&#123;<\/span> type<span class=\"sy0\">:<\/span> <span class=\"st0\">'REDO'<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\ntype AddTaskEvent <span class=\"sy0\">=<\/span> <span class=\"br0\">&#123;<\/span> type<span class=\"sy0\">:<\/span> <span class=\"st0\">'ADD_TASK'<\/span><span class=\"sy0\">;<\/span> task<span class=\"sy0\">:<\/span> SerializedTask <span class=\"br0\">&#125;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\ntype UpsertTaskEvent <span class=\"sy0\">=<\/span> <span class=\"br0\">&#123;<\/span> type<span class=\"sy0\">:<\/span> <span class=\"st0\">'UPSERT_TASK'<\/span><span class=\"sy0\">;<\/span> task<span class=\"sy0\">:<\/span> SerializedTask <span class=\"br0\">&#125;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\ntype DeleteTaskEvent <span class=\"sy0\">=<\/span> <span class=\"br0\">&#123;<\/span> type<span class=\"sy0\">:<\/span> <span class=\"st0\">'DELETE_TASK'<\/span><span class=\"sy0\">;<\/span> id<span class=\"sy0\">:<\/span> string <span class=\"sy0\">|<\/span> number <span class=\"br0\">&#125;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\ntype AddLinkEvent <span class=\"sy0\">=<\/span> <span class=\"br0\">&#123;<\/span> type<span class=\"sy0\">:<\/span> <span class=\"st0\">'ADD_LINK'<\/span><span class=\"sy0\">;<\/span> link<span class=\"sy0\">:<\/span> Link <span class=\"br0\">&#125;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\ntype UpsertLinkEvent <span class=\"sy0\">=<\/span> <span class=\"br0\">&#123;<\/span> type<span class=\"sy0\">:<\/span> <span class=\"st0\">'UPSERT_LINK'<\/span><span class=\"sy0\">;<\/span> link<span class=\"sy0\">:<\/span> Link <span class=\"br0\">&#125;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\ntype DeleteLinkEvent <span class=\"sy0\">=<\/span> <span class=\"br0\">&#123;<\/span> type<span class=\"sy0\">:<\/span> <span class=\"st0\">'DELETE_LINK'<\/span><span class=\"sy0\">;<\/span> id<span class=\"sy0\">:<\/span> string <span class=\"sy0\">|<\/span> number <span class=\"br0\">&#125;<\/span><span class=\"sy0\">;<\/span><br \/>\n<br \/>\ntype EventType <span class=\"sy0\">=<\/span> &nbsp;<br \/>\n&nbsp; <span class=\"sy0\">|<\/span> SetZoomEvent &nbsp;<br \/>\n&nbsp; <span class=\"sy0\">|<\/span> UndoEvent &nbsp;<br \/>\n&nbsp; <span class=\"sy0\">|<\/span> RedoEvent &nbsp;<br \/>\n&nbsp; <span class=\"sy0\">|<\/span> AddTaskEvent &nbsp;<br \/>\n&nbsp; <span class=\"sy0\">|<\/span> UpsertTaskEvent &nbsp;<br \/>\n&nbsp; <span class=\"sy0\">|<\/span> DeleteTaskEvent &nbsp;<br \/>\n&nbsp; <span class=\"sy0\">|<\/span> AddLinkEvent &nbsp;<br \/>\n&nbsp; <span class=\"sy0\">|<\/span> UpsertLinkEvent &nbsp;<br \/>\n&nbsp; <span class=\"sy0\">|<\/span> DeleteLinkEvent<span class=\"sy0\">;<\/span><\/div><\/div>\n<p>Events cause Gantt state transitions and trigger corresponding actions that update the context. Each action defines how the state changes. For instance, here is how task-related actions are implemented:<\/p>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\">addTask<span class=\"sy0\">:<\/span> assign<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span> context<span class=\"sy0\">:<\/span> ctx<span class=\"sy0\">,<\/span> event <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span> <span class=\"sy0\">=&gt;<\/span> <span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; tasks<span class=\"sy0\">:<\/span> <span class=\"br0\">&#91;<\/span>...<span class=\"me1\">ctx<\/span>.<span class=\"me1\">tasks<\/span><span class=\"sy0\">,<\/span> <span class=\"br0\">&#123;<\/span> ...<span class=\"br0\">&#40;<\/span>event as AddTaskEvent<span class=\"br0\">&#41;<\/span>.<span class=\"me1\">task<\/span><span class=\"sy0\">,<\/span> id<span class=\"sy0\">:<\/span> `DB_ID<span class=\"sy0\">:<\/span>$<span class=\"br0\">&#123;<\/span><span class=\"br0\">&#40;<\/span>event as AddTaskEvent<span class=\"br0\">&#41;<\/span>.<span class=\"me1\">task<\/span>.<span class=\"me1\">id<\/span><span class=\"br0\">&#125;<\/span>` <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#93;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n<span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">,<\/span><br \/>\n<br \/>\nupsertTask<span class=\"sy0\">:<\/span> assign<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span> context<span class=\"sy0\">:<\/span> ctx<span class=\"sy0\">,<\/span> event <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span> <span class=\"sy0\">=&gt;<\/span> <span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; tasks<span class=\"sy0\">:<\/span> ctx.<span class=\"me1\">tasks<\/span>.<span class=\"me1\">map<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#40;<\/span>task<span class=\"br0\">&#41;<\/span> <span class=\"sy0\">=&gt;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; <span class=\"kw4\">String<\/span><span class=\"br0\">&#40;<\/span>task.<span class=\"me1\">id<\/span><span class=\"br0\">&#41;<\/span> <span class=\"sy0\">===<\/span> <span class=\"kw4\">String<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#40;<\/span>event as UpsertTaskEvent<span class=\"br0\">&#41;<\/span>.<span class=\"me1\">task<\/span>.<span class=\"me1\">id<\/span><span class=\"br0\">&#41;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"sy0\">?<\/span> <span class=\"br0\">&#123;<\/span> ...<span class=\"me1\">task<\/span><span class=\"sy0\">,<\/span> ...<span class=\"br0\">&#40;<\/span>event as UpsertTaskEvent<span class=\"br0\">&#41;<\/span>.<span class=\"me1\">task<\/span> <span class=\"br0\">&#125;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"sy0\">:<\/span> task &nbsp;<br \/>\n&nbsp; <span class=\"br0\">&#41;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n<span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">,<\/span><br \/>\n<br \/>\ndeleteTask<span class=\"sy0\">:<\/span> assign<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span> context<span class=\"sy0\">,<\/span> event <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span> <span class=\"sy0\">=&gt;<\/span> <span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; tasks<span class=\"sy0\">:<\/span> context.<span class=\"me1\">tasks<\/span>.<span class=\"me1\">filter<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#40;<\/span>t<span class=\"br0\">&#41;<\/span> <span class=\"sy0\">=&gt;<\/span> <span class=\"kw4\">String<\/span><span class=\"br0\">&#40;<\/span>t.<span class=\"me1\">id<\/span><span class=\"br0\">&#41;<\/span> <span class=\"sy0\">!==<\/span> <span class=\"kw4\">String<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#40;<\/span>event as DeleteTaskEvent<span class=\"br0\">&#41;<\/span>.<span class=\"me1\">id<\/span><span class=\"br0\">&#41;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n<span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">,<\/span><\/div><\/div>\n<p>Actions for link updates follow the same pattern. When the context must be updated, it is required to use the <strong>assign<\/strong> function, which creates a new context version based on the current state and the actuated event.  <\/p>\n<h3>Creating the Machine Configuration<\/h3>\n<p>Now, it is time to show how to bring it all together by creating the state machine configuration:<\/p>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;height:600px;\"><div class=\"javascript codecolorer\"><span class=\"kw1\">const<\/span> createSnapshot <span class=\"sy0\">=<\/span> <span class=\"br0\">&#40;<\/span>ctx<span class=\"sy0\">:<\/span> ContextType<span class=\"br0\">&#41;<\/span><span class=\"sy0\">:<\/span> Snapshot <span class=\"sy0\">=&gt;<\/span> <span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; tasks<span class=\"sy0\">:<\/span> structuredClone<span class=\"br0\">&#40;<\/span>ctx.<span class=\"me1\">tasks<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; links<span class=\"sy0\">:<\/span> structuredClone<span class=\"br0\">&#40;<\/span>ctx.<span class=\"me1\">links<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; config<span class=\"sy0\">:<\/span> structuredClone<span class=\"br0\">&#40;<\/span>ctx.<span class=\"me1\">config<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n<span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n<br \/>\n<span class=\"kw5\">export<\/span> <span class=\"kw1\">const<\/span> ganttMachine <span class=\"sy0\">=<\/span> createMachine<span class=\"br0\">&#40;<\/span> &nbsp;<br \/>\n&nbsp; <span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; id<span class=\"sy0\">:<\/span> <span class=\"st0\">'gantt'<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; types<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; context<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span><span class=\"br0\">&#125;<\/span> as ContextType<span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; events<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span><span class=\"br0\">&#125;<\/span> as EventType<span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; context<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; tasks<span class=\"sy0\">:<\/span> seedTasks<span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; links<span class=\"sy0\">:<\/span> seedLinks<span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; config<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span> zoom<span class=\"sy0\">:<\/span> defaultZoomLevels <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; past<span class=\"sy0\">:<\/span> <span class=\"br0\">&#91;<\/span><span class=\"br0\">&#93;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; future<span class=\"sy0\">:<\/span> <span class=\"br0\">&#91;<\/span><span class=\"br0\">&#93;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; maxHistory<span class=\"sy0\">:<\/span> <span class=\"nu0\">50<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; initial<span class=\"sy0\">:<\/span> <span class=\"st0\">'ready'<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; states<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; ready<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; on<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; SET_ZOOM<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span> actions<span class=\"sy0\">:<\/span> <span class=\"br0\">&#91;<\/span><span class=\"st0\">'pushHistory'<\/span><span class=\"sy0\">,<\/span> <span class=\"st0\">'setZoom'<\/span><span class=\"br0\">&#93;<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; UNDO<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span> actions<span class=\"sy0\">:<\/span> <span class=\"st0\">'undo'<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; REDO<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span> actions<span class=\"sy0\">:<\/span> <span class=\"st0\">'redo'<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><br \/>\n<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ADD_TASK<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span> actions<span class=\"sy0\">:<\/span> <span class=\"br0\">&#91;<\/span><span class=\"st0\">'pushHistory'<\/span><span class=\"sy0\">,<\/span> <span class=\"st0\">'addTask'<\/span><span class=\"br0\">&#93;<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; UPSERT_TASK<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span> actions<span class=\"sy0\">:<\/span> <span class=\"br0\">&#91;<\/span><span class=\"st0\">'pushHistory'<\/span><span class=\"sy0\">,<\/span> <span class=\"st0\">'upsertTask'<\/span><span class=\"br0\">&#93;<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; DELETE_TASK<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span> actions<span class=\"sy0\">:<\/span> <span class=\"br0\">&#91;<\/span><span class=\"st0\">'pushHistory'<\/span><span class=\"sy0\">,<\/span> <span class=\"st0\">'deleteTask'<\/span><span class=\"br0\">&#93;<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><br \/>\n<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ADD_LINK<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span> actions<span class=\"sy0\">:<\/span> <span class=\"br0\">&#91;<\/span><span class=\"st0\">'pushHistory'<\/span><span class=\"sy0\">,<\/span> <span class=\"st0\">'addLink'<\/span><span class=\"br0\">&#93;<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; UPSERT_LINK<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span> actions<span class=\"sy0\">:<\/span> <span class=\"br0\">&#91;<\/span><span class=\"st0\">'pushHistory'<\/span><span class=\"sy0\">,<\/span> <span class=\"st0\">'upsertLink'<\/span><span class=\"br0\">&#93;<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; DELETE_LINK<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span> actions<span class=\"sy0\">:<\/span> <span class=\"br0\">&#91;<\/span><span class=\"st0\">'pushHistory'<\/span><span class=\"sy0\">,<\/span> <span class=\"st0\">'deleteLink'<\/span><span class=\"br0\">&#93;<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><br \/>\n<span class=\"br0\">&#41;<\/span><\/div><\/div>\n<p>The machine operates within a single <strong>ready<\/strong> state where all Gantt interactions are handled. Each event is mapped to one or more actions. The <strong>context<\/strong> object includes the initial data for the state machine.<\/p>\n<p>Unlike Redux Toolkit or Zustand, where you directly call functions like <strong>addTask()<\/strong> or <strong>setZoom()<\/strong> to update state, you define a fixed set of events and then send them to the machine. It uses the current state and the incoming event to determine the next state. This makes all transitions easier to track as the application grows.<\/p>\n<h3>Connecting Gantt to the State Machine<\/h3>\n<p>This step is vital for understanding how changes in the Gantt chart reach the XState machine. In React Gantt, all data-related changes go through the <strong>data.save<\/strong> callback:<\/p>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\"><span class=\"kw1\">const<\/span> data<span class=\"sy0\">:<\/span> ReactGanttProps<span class=\"br0\">&#91;<\/span><span class=\"st0\">'data'<\/span><span class=\"br0\">&#93;<\/span> <span class=\"sy0\">=<\/span> useMemo<span class=\"br0\">&#40;<\/span> &nbsp;<br \/>\n&nbsp; <span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span> <span class=\"sy0\">=&gt;<\/span> <span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; save<span class=\"sy0\">:<\/span> <span class=\"br0\">&#40;<\/span>entity<span class=\"sy0\">,<\/span> action<span class=\"sy0\">,<\/span> item<span class=\"sy0\">,<\/span> id<span class=\"br0\">&#41;<\/span> <span class=\"sy0\">=&gt;<\/span> <span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"kw1\">if<\/span> <span class=\"br0\">&#40;<\/span>entity <span class=\"sy0\">===<\/span> <span class=\"st0\">'task'<\/span><span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"kw1\">const<\/span> task <span class=\"sy0\">=<\/span> item as SerializedTask<span class=\"sy0\">;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"kw1\">if<\/span> <span class=\"br0\">&#40;<\/span>action <span class=\"sy0\">===<\/span> <span class=\"st0\">'create'<\/span><span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; send<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span> type<span class=\"sy0\">:<\/span> <span class=\"st0\">'ADD_TASK'<\/span><span class=\"sy0\">,<\/span> task <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span> <span class=\"kw1\">else<\/span> <span class=\"kw1\">if<\/span> <span class=\"br0\">&#40;<\/span>action <span class=\"sy0\">===<\/span> <span class=\"st0\">'update'<\/span><span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; send<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span> type<span class=\"sy0\">:<\/span> <span class=\"st0\">'UPSERT_TASK'<\/span><span class=\"sy0\">,<\/span> task <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span> <span class=\"kw1\">else<\/span> <span class=\"kw1\">if<\/span> <span class=\"br0\">&#40;<\/span>action <span class=\"sy0\">===<\/span> <span class=\"st0\">'delete'<\/span><span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; send<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span> type<span class=\"sy0\">:<\/span> <span class=\"st0\">'DELETE_TASK'<\/span><span class=\"sy0\">,<\/span> id <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span> <span class=\"kw1\">else<\/span> <span class=\"kw1\">if<\/span> <span class=\"br0\">&#40;<\/span>entity <span class=\"sy0\">===<\/span> <span class=\"st0\">'link'<\/span><span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"kw1\">const<\/span> link <span class=\"sy0\">=<\/span> item as Link<span class=\"sy0\">;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"kw1\">if<\/span> <span class=\"br0\">&#40;<\/span>action <span class=\"sy0\">===<\/span> <span class=\"st0\">'create'<\/span><span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; send<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span> type<span class=\"sy0\">:<\/span> <span class=\"st0\">'ADD_LINK'<\/span><span class=\"sy0\">,<\/span> link <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span> <span class=\"kw1\">else<\/span> <span class=\"kw1\">if<\/span> <span class=\"br0\">&#40;<\/span>action <span class=\"sy0\">===<\/span> <span class=\"st0\">'update'<\/span><span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; send<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span> type<span class=\"sy0\">:<\/span> <span class=\"st0\">'UPSERT_LINK'<\/span><span class=\"sy0\">,<\/span> link <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span> <span class=\"kw1\">else<\/span> <span class=\"kw1\">if<\/span> <span class=\"br0\">&#40;<\/span>action <span class=\"sy0\">===<\/span> <span class=\"st0\">'delete'<\/span><span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; send<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span> type<span class=\"sy0\">:<\/span> <span class=\"st0\">'DELETE_LINK'<\/span><span class=\"sy0\">,<\/span> id <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span> &nbsp;<br \/>\n&nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">,<\/span> &nbsp;<br \/>\n&nbsp; <span class=\"br0\">&#91;<\/span>send<span class=\"br0\">&#93;<\/span> &nbsp;<br \/>\n<span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><\/div><\/div>\n<p>The callback fires whenever a task or a link is created, edited, or removed. These changes are passed to XState by sending events via the <strong>send<\/strong> function. <\/p>\n<p>The last thing to note here is the Gantt component&#8217;s visual responsibility. It doesn\u2019t store or modify data, but simply renders what it receives. Tasks, links, and configuration changes come in as props, and the component updates the UI when props change.<\/p>\n<p>Here is the complete workflow of event-driven state management for DHTMLX React Gantt, which is applicable for React Scheduler as well:<\/p>\n<p><strong>Data-changing user interaction with the UI \u2192 captured by data.save \u2192 transformed into an event \u2192 processed by the state machine for React Gantt \u2192 context is updated via actions \u2192 Gantt UI re-renders with new props.<\/strong><\/p>\n<h2>Conclusion<\/h2>\n<p>XState may not be the most straightforward option for every project, but it becomes a strong choice in scenarios with intricate logic. In applications built around UI components like React Gantt or Scheduler, where a lot of things depend on each other, this approach provides a more predictable and scalable way to manage state than more conventional solutions. If your project involves complex UI workflows with coordinated state changes, XState is often a better fit. At the same time, for simpler use cases, XState may introduce unnecessary complexity, so it should be chosen deliberately based on the needs of your application.<\/p>\n<h2>Related Materials<\/h2>\n<ul>\n<li><a href=\"https:\/\/docs.dhtmlx.com\/gantt\/integrations\/react\/\" rel=\"noopener\" target=\"_blank\">DHTMLX React Gantt documentation<\/a><\/li>\n<li><a href=\"https:\/\/www.npmjs.com\/package\/@dhtmlx\/trial-react-gantt\" rel=\"noopener\" target=\"_blank\">React Gantt NPM package (trial version)<\/a><\/li>\n<li><a href=\"https:\/\/dhtmlx.com\/blog\/using-zustand-state-management-apps-dhtmlx-react-gantt-scheduler\/\" rel=\"noopener\" target=\"_blank\">Using Zustand for State Management in Apps with DHTMLX React Gantt and Scheduler<\/a><\/li>\n<li><a href=\"https:\/\/dhtmlx.com\/blog\/managing-state-dhtmlx-react-gantt-chart-scheduler-mobx\/\" rel=\"noopener\" target=\"_blank\">Managing State in DHTMLX React Gantt Chart and Scheduler with MobX<\/a><\/li>\n<li><a href=\"https:\/\/dhtmlx.com\/blog\/using-dhtmlx-gantt-chart-for-react-with-redux-toolkit-principles-benefits-and-implementation-tips\/\" rel=\"noopener\" target=\"_blank\">Using DHTMLX Gantt Chart for React with Redux Toolkit: Principles, Benefits, and Implementation Tips<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>When building a project planning application in React with components like a Gantt chart, state management quickly becomes a complex problem: the basic approach does not scale well for &#8230;<\/p>\n","protected":false},"author":101014,"featured_media":15418,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[71,1],"tags":[33,20,56,15],"class_list":["post-15403","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorials","category-uncategorized","tag-all-posts","tag-gantt","tag-react","tag-dhtmlxscheduler"],"_links":{"self":[{"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/posts\/15403","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/users\/101014"}],"replies":[{"embeddable":true,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/comments?post=15403"}],"version-history":[{"count":4,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/posts\/15403\/revisions"}],"predecessor-version":[{"id":15422,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/posts\/15403\/revisions\/15422"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/media\/15418"}],"wp:attachment":[{"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/media?parent=15403"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/categories?post=15403"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/tags?post=15403"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}