Skip to main content

The right way to check for empty content in Twig

Have you ever used {% if content|render|trim is not empty %} in twig to validate content is not empty?

If so, you might end up with some surprises related to the use of the render method.
 

by mohit.aghera /

Introduction

Checking that content is not empty before rendering is good practice. Typically we call the “trim” method as well so all the space gets trimmed and we get an accurate indication if the content is empty.

Usually, developers uses snippet like {% if content|render|trim is not empty %} to ensure that content is not empty.

Recently while working on one of our projects, the client informed us that Webform was having issues with Recaptcha and was throwing errors related to invalid tokens. To debug further, we disabled recaptcha handler and saw that Webform submissions were being recorded twice.

While debugging further, we noticed that all the things were working as expected for Webform and simple_recaptcha module’s Webform handler. So both of those causes were out of the picture.

We'd experienced a similar issue before, and in that case the cause was a [node:description] tag from Metatags that was rendering the embedded Webform. At first we thought this might have been the case, but debugging revealed that Metatags was not in the mix this time.

During debugging further around rendering process, we noticed that render() was being called twice. Rendering twice was causing issues with recaptcha tokens. Because the second render was invalidating the original recaptcha token.

Upon debugging further we found the following twig templates code was causing issues. 
 

​​{% if content|render|trim is not empty %} 
<div{{ attributes }}> 
 <div{{ region_attributes.content }}> 
 {{ content.content }} 
 </div> 
</div> 
{% endif %} 
 

Here, if you notice the block {% if content|render|trim is not empty %} that we are calling render method of TwigExtension.php. So this was rendering the content the first time and then the second time we are actually rendering the content using {{ content.content }}. So this second render was invalidating the previous token generated by the Webform handler.

Solution

As the use of the render function was causing issues we need to use a different approach to validate the issue.

In such situations twig’s set tag comes to the rescue.

We can use it in conjunction with the spaceless twig filter to only render the content once. The spaceless filter makes sure we don't get a false positive from whitespace, just like the trim function did before.

So we can refactor above twig snippet using the set tag and the spaceless filter like this:

{% set content_render %}{% apply spaceless %}
 {{ content.content }}
{% endapply %}{% endset %}
{% if content_render is not empty %}
 <div{{ attributes }}>
   <div{{ region_attributes.content }}>
     {{ content_render }}
   </div>
 </div>
{% endif %}

By using this approach, we can achieve the same thing as the original approach, but with only one render.

Update

Another approach is to use is directly set statement. It also gives similar results. Like we discussed earlier, the main gist is to avoid double render which might cause unexpected issues.

{% set content_render = content.content|render %}
{% if content_render|trim %}
  {{ content_render }}
{% endif %}

Thanks to Hawkeye Tenderwolf for the suggestion.

Conclusion

The set tag in conjunction with the spaceless filter allows us to safely render a variable, remove any whitespace between HTML tags and then use that as a variable to evaluate if the output is empty. If not, we can just output the already-rendered variable and in doing so avoid additional render in our twig templates. 
 

Posted by mohit.aghera
Senior Drupal Developer

Dated

Comments

Comment by Marc

Dated

Thanks for the blog post, Mohit!

Personally I like a more compact syntax for setting the variable: `{% set content_render %}{{- content.content -}}{% endset %}` (see https://twig.symfony.com/doc/2.x/templates.html#whitespace-control) or `{% set content_render = content.content|render|trim %}`. But maybe that's just a matter of taste.

It's worth mentioning that there are some Drupal.org issues about this topic. In https://www.drupal.org/project/drupal/issues/2547559 you'll find a couple of different methods, some better than others, including the one you described here. It is marked as duplicate of https://www.drupal.org/project/drupal/issues/953034, which is where the real work happens. The issue summary of #953034 also explains the limitations of the solution above (tip: search for the word "placeholder").

Comment by Hawkeye Tenderwolf

Dated

@Mohit, do you mind updating your article to remove the part about using the set tag? Using the set statement would be considered much more standard. To avoid double-rendering, just store the string output of the first render into a variable. E.g.,

{% set content_render = content.content|render %}
{# "content_render" is now just a string and can be used/printed as many times as you like. #}
{% if content_render|trim %}

{{ content_render }}

{% endif %}

Comment by mohit.aghera

Dated

Thanks for the suggestion, Hawkeye Tenderwolf.

I have updated the article and included your suggestion as well.

Pagination