Turbo Streams Duplicate Element When Appending/Prepending

In January, 2021 a bug was reported in Turbo that caused duplicate DOM elements. In this post we’ll explain the bug and how it was fixed.

Duplicate element bug

The bug occurs when Turbo recieves an append/prepend action for an element which is already in the DOM. This can happen when using Turbo with background jobs. If the user refreshes before the background job completes, the element will already be on the page. This means Turbo recieves an append/prepend action for an element that’s already on the page. Before the bug was fixed, this scenario caused a double render.

Given this list:

1
2
3
4
<ul id="products">
  <li id="product_1">hat</li>
  <li id="product_2">coat</li>
</ul>

If Turbo recieves the following Stream element:

1
2
3
4
5
6
<turbo-stream action="append" target="products">
  <template>
      <li id="product_1">shiny hat</li>
      <li id="product_3">socks</li>
  </template>
</turbo-stream>

The DOM will re-render the list as:

1
2
3
4
5
6
<ul id="products">
  <li id="product_1">hat</li>
  <li id="product_2">coat</li>
  <li id="product_1">shiny hat</li>
  <li id="product_3">socks</li>
</ul>

We can see product_1 has been duplicated instead of updated.

The Fix

This bug has now been fixed. Given the same scenario, Turbo will re-render the DOM correctly:

1
2
3
4
5
<ul id="products">
  <li id="product_2">coat</li>
  <li id="product_1">shiny hat</li>
  <li id="product_3">socks</li>
</ul>

The fix introduces a new removeDuplicateTargetChildren function, which is called before any Stream append or prepend. See the PR for details.