Attributes are shared between sdc instances

Created on 15 December 2023, 6 months ago
Updated 18 June 2024, 8 days ago

Problem/Motivation

The attribute of a single directory component is shared and not reset between instances, at least on the same page.
This leads to unexpected behaviors, if the developer alter them in the twig file.

Steps to reproduce

Set a basic sdc.

'$schema': 'https://git.drupalcode.org/project/drupal/-/raw/10.1.x/core/modules/sdc/src/metadata.schema.json'
name: test
status: stable
props:
  type: object
  properties:
    my_classes:
      type: array
      items:
        type: string
<div {{ attributes.addClass(my_classes) }}>
  {# attributes defined by sdc are problematic #}
  {{ dd(attributes.getClass) }}

  {# I won't add attributes created manually in the results below, they work as expected #}
  {% set my_attribute = create_attribute() %}
  {% do my_attribute.addClass(my_classes) %}
  {{ dd(my_attribute.getClass) }}

  {% block example_block %}
    The contents of the example block
  {% endblock %}
</div>

Include or embed that sdc into a page, multiple time.
E.g. inside node.hmtl.twig put:

{{ include('olivero:test', { my_classes: ['class-1', 'class-2']}) }}

{{ include('olivero:test', { my_classes: ['class-3', 'class-4']}) }}

{{ include('olivero:test') }}

Expected (?) result of dd(attributes.getClass)

 Drupal\Core\Template\AttributeArray {#2103 ▼
  #value: array:3 [▼
    0 => "contextual-region"
    1 => "class-1"
    2 => "class-2"
  ]
  #name: "class"
}

 Drupal\Core\Template\AttributeArray {#2142 ▼
  #value: array:5 [▼
    0 => "contextual-region"
    3 => "class-3"
    4 => "class-4"
  ]
  #name: "class"
}

 Drupal\Core\Template\AttributeArray {#2083 ▼
  #value: array:1 [▼
    0 => "contextual-region"
  ]
  #name: "class"
}

Actual result of dd(attributes.getClass)

 Drupal\Core\Template\AttributeArray {#2103 ▼
  #value: array:3 [▼
    0 => "contextual-region"
    1 => "class-1"
    2 => "class-2"
  ]
  #name: "class"

 Drupal\Core\Template\AttributeArray {#2142 ▼
  #value: array:5 [▼
    0 => "contextual-region"
    1 => "class-1"
    2 => "class-2"
    3 => "class-3"
    4 => "class-4"
  ]
  #name: "class"
}

 Drupal\Core\Template\AttributeArray {#1641 ▼
  #value: array:5 [▼
    0 => "contextual-region"
    1 => "class-1"
    2 => "class-2"
    3 => "class-3"
    4 => "class-4"
  ]
  #name: "class"
}

Proposed resolution

The altering a sdc' attribute isn't documented (yet) anywhere, but I took for granted, as I was expecting a full separation of context from them.
I'd suggest they should have a "clean state" between instances.

🐛 Bug report
Status

Active

Version

11.0 🔥

Component
single-directory components 

Last updated about 21 hours ago

Created by

🇮🇹Italy Giuseppe87

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • Issue created by @Giuseppe87
  • Status changed to Postponed: needs info 6 months ago
  • 🇦🇺Australia larowlan 🇦🇺🏝.au GMT+10

    Does this still happen if you use the only keyword in your include statements https://twig.symfony.com/doc/3.x/tags/include.html

  • 🇮🇹Italy Giuseppe87

    Either {% include 'template.html' with {'foo': 'bar'} only %} or {{ include('template.html', {foo: 'bar'}, with_context = false) }} help to mitigate the issue.

    Namely:

    {{ include('olivero:test', { my_classes: ['class-1', 'class-2']}, with_context = false) }}
    
    {{ include('olivero:test') }}
    
    {{ include('olivero:test', with_context = false) }}
    

    The second and third include won't have the classes of the first.

    {{ include('olivero:test', { my_classes: ['class-1', 'class-2']}) }}
    
    {{ include('olivero:test') }}
    
    {{ include('olivero:test', with_context = false) }}
    

    The second include will have the class of the first, the third won't have them.

    I'm saying mitigate because:

    • the context is passed by default, so it this sdc's attribute behavior is considered a good pattern and not a bug, it should at be at least documented
    • relying on disabling the context force to pass props naturally available. For example, I discovered this behavior while placing multiple instance of the same sdc inside user.html.twig, where I am using the user variable, already present inside the context
  • Status changed to Active 6 months ago
  • 🇦🇺Australia larowlan 🇦🇺🏝.au GMT+10

    I think what is needed here is a preprocess hook for all SDC components that creates a new Attributes variable.

    That's what most theme hooks have to prevent the leakage by default.

  • 🇮🇹Italy Giuseppe87

    Using a bit more sdc, I've found another leakage.
    I'm also putting into this issue because the cause and solution may be the same.
    It's a very weird and corner case I got using the set command.

    You need two templates: the first, outer_sdc must use a set, e.g.:

        {% set my_content %}
            <div class="my-content">
                {% block my_block %}
                    My test sdc
                {% endblock %}
            </div>
        {% endset %}
    
    
        <div {{ attributes }}>
            {% if 1 > 0 %}
                <div>
                    {{ my_content }}
                </div>
            {% else %}
                <span>
         {{ my_content }}
      </span>
            {% endif %}
        </div>
    

    This sdc must be embeded in another sdc/theme twig. The block section must include another sdc:

        {% embed 'my_theme:outer_sdc' %}
        {% block my_block %}
    {{ include(my_theme:base_sdc') }}
            {% endblock %}
          {% endembed %}
    

    base_sdc can be a skeleton:

    <div {{ attributes }}>
      {% block example_block %}
        The contents of the example block
      {% endblock %}
    </div>
    

    The expected output should be:

    <!-- 🥘 Component start: my_theme:outer_sdc -->
    <div data-component-id="my_theme:outer_sdc">
        <div class="my-content">
            <!-- 🥓 Component start: my_theme:base_sdc -->
            <div data-component-id="my_theme:base">
                The contents of the example block
            </div>
            <!-- 🥓 Component end: my_theme:base -->
        </div>
    </div>
    <!-- 🥘 Component end: my_theme:outer_sdc -->
    

    But actually is

    <!-- 🥘 Component start: my_theme:outer_sdc -->
    <div data-component-id="my_theme:base">
        <div>
            <div class="my-content">
                <!-- 🥓 Component start: my_theme:base -->
                <div data-component-id="my_theme:base">
                    The contents of the example block
                </div>
                <!-- 🥓 Component end: my_theme:base -->
            </div>
        </div>
    </div>
    <!-- 🥘 Component end: my_theme:outer_sdc -->
    

    Namely, the <div data-component-id="my_theme:outer_sdc"> has been printed as <div data-component-id="my_theme:base">

    By the way, this problem doesn't depend on having print my_content twice conditionally.
    For example if outer_sdc is

    {% set my_content %}
      {% block my_block %}
        LOREM IPSUM
      {% endblock %}
    {% endset %}
    
    
    <div {{ attributes }}>
      <div class="my-content">
        {{ my_content }}
      </div>
    </div>
    

    The problem is the same, so the problem is something about the set command.

  • 🇺🇸United States xjm
Production build 0.69.0 2024