> For the complete documentation index, see [llms.txt](https://samuel-arbibe.gitbook.io/dnd-timeline/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://samuel-arbibe.gitbook.io/dnd-timeline/1/documentation/zoom-and-pan/performance.md).

# Performance

Whenever the `timeframe` changes, a recalculation of all the item's width and position must be performed.

When performing operations that require a high amount of sequential changes, like zooming in or panning, the resulted re-renders might cause stuttering and frame-drops.

This **"frequent change"** problem is quite common in reactive frontend applications, and there are multiple solutions to it.

As you are in full control of the `timeframe` state, you can apply a chosen mechanism of debouncing to the `timeframe` state.

This will reduce the amount changes registered in the timeline, and in return reduce the amount of renders required when zooming an panning, resulting in a great improvement in performance.

Here are some common solutions:

{% tabs %}
{% tab title="Deferring ⭐️" %}
If you use **React 18+**, you should make use of the [`useDeferredValue`](https://react.dev/reference/react/useDeferredValue) API.

> `useDeferredValue` is a React Hook that lets you defer updating a part of the UI.

#### Code Example

{% tabs %}
{% tab title="App.tsx" %}

<pre class="language-tsx"><code class="lang-tsx">function App() {
  const [timeframe, setTimeframe] = useState(DEFAULT_TIMEFRAME);
<strong>  const debouncedTimeframe = useDeferredValue(timeframe);
</strong>  
  ...
  
  return (
    &#x3C;TimelineContext
        onDragEnd={onDragEnd}
        onResizeEnd={onResizeEnd}
<strong>        timeframe={debouncedTimeframe} // provide the debounced timeframe
</strong>        onTimeframeChanged={setTimeframe}
    >
      &#x3C;Timeline items={items} rows={rows} />
    &#x3C;/TimelineContext>
  )
</code></pre>

{% endtab %}

{% tab title="Item.tsx" %}

<pre class="language-tsx"><code class="lang-tsx">function Item(props: ItemProps) {
  ...
  const style: CSSProperties = {
    ...itemStyle,
<strong>    transition: 'left .2s linear, width .2s linear', 
</strong><strong>    // You can a CSS transition to make the debounce feel smoother
</strong>  }

  return (
    &#x3C;div ref={setNodeRef} style={style} {...listeners} {...attributes}>
    ...
    &#x3C;/div>
  )
}
</code></pre>

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="Throttle" %}
Another common solution is **throttling.**

> Throttling limits the rate of function calls. It guarantees that a function is only executed once within a set time interval. If the function is called multiple times during that interval, only the first call is executed. Subsequent calls are ignored until the interval has elapsed.

#### Code Example

{% tabs %}
{% tab title="App.tsx" %}

<pre class="language-tsx"><code class="lang-tsx">function App() {
  const [timeframe, setTimeframe] = useState(DEFAULT_TIMEFRAME);
<strong>  const debouncedTimeframe = useThrottle(timeframe, 300);
</strong>  
  ...
  
  return (
    &#x3C;TimelineContext
        onDragEnd={onDragEnd}
        onResizeEnd={onResizeEnd}
<strong>        timeframe={debouncedTimeframe} // provide the debounced timeframe
</strong>        onTimeframeChanged={setTimeframe}
    >
      &#x3C;Timeline items={items} rows={rows} />
    &#x3C;/TimelineContext>
  )
</code></pre>

{% endtab %}

{% tab title="Item.tsx" %}

<pre class="language-tsx"><code class="lang-tsx">function Item(props: ItemProps) {
  ...
  const style: CSSProperties = {
    ...itemStyle,
<strong>    transition: 'left .2s linear, width .2s linear', 
</strong><strong>    // You can a CSS transition to make the debounce feel smoother
</strong>  }

  return (
    &#x3C;div ref={setNodeRef} style={style} {...listeners} {...attributes}>
    ...
    &#x3C;/div>
  )
}
</code></pre>

{% endtab %}

{% tab title="useThrottle.tsx" %}

```tsx
export function useThrottle<T>(value: T, interval = 500) {
  const [throttledValue, setThrottledValue] = useState(value);
  const lastUpdated = useRef<number | null>(null);

  useEffect(() => {
    const now = Date.now();

    if (lastUpdated.current && now >= lastUpdated.current + interval) {
      lastUpdated.current = now;
      setThrottledValue(value);
    } else {
      const id = window.setTimeout(() => {
        lastUpdated.current = now;
        setThrottledValue(value);
      }, interval);

      return () => {
        window.clearTimeout(id)
      };
    }
  }, [value, interval]);

  return throttledValue;
}
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="Debounce" %}
The most common way to solve this is using **debounce.**

> Debouncing prevents extra activations or slow functions from triggering too often. It will wait for a given interval to elapse since the last change, and only then apply it.

#### Code Example

{% tabs %}
{% tab title="App.tsx" %}

<pre class="language-tsx"><code class="lang-tsx">function App() {
  const [timeframe, setTimeframe] = useState(DEFAULT_TIMEFRAME);
<strong>  const debouncedTimeframe = useDebounce(timeframe, 300)
</strong>  
  ...
  
  return (
    &#x3C;TimelineContext
        onDragEnd={onDragEnd}
        onResizeEnd={onResizeEnd}
<strong>        timeframe={debouncedTimeframe} // provide the debounced timeframe
</strong>        onTimeframeChanged={setTimeframe}
    >
      &#x3C;Timeline items={items} rows={rows} />
    &#x3C;/TimelineContext>
  )
</code></pre>

{% endtab %}

{% tab title="Item.tsx" %}

<pre class="language-tsx"><code class="lang-tsx">function Item(props: ItemProps) {
  ...
  const style: CSSProperties = {
    ...itemStyle,
<strong>    transition: 'left .2s linear, width .2s linear', 
</strong><strong>    // You can a CSS transition to make the debounce feel smoother
</strong>  }

  return (
    &#x3C;div ref={setNodeRef} style={style} {...listeners} {...attributes}>
    ...
    &#x3C;/div>
  )
}
</code></pre>

{% endtab %}

{% tab title="useDebounce.tsx" %}

```tsx
export function useDebounce<T>(value: T, delay = 500) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}
```

{% endtab %}
{% endtabs %}
{% endtab %}
{% endtabs %}

> 🧠 You can still make use of the un-debounced state to render selected components in real-time. For example, you can render the time-axis using the un-debounced state, and render the timeline using the debounced state.
>
> Play around with the live demo, and watch how the timeaxis and the timeframe move asynchronously🔥

### Live Example

In this example, You can play around with difference modes and feel the difference between them.

[Click here to see the full code](https://github.com/samuelarbibe/dnd-timeline/blob/main/docs/examples/performance/src/App.tsx)

{% embed url="<https://dnd-timeline-performance.vercel.app>" %}
