├── images └── logos │ └── social.png ├── LICENSE └── README.md /images/logos/social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-schranz/twig-for-react-devs/HEAD/images/logos/social.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Alexander Schranz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Twig for react devs 2 | 3 | An easy guide for react developers getting into PHP Twig template engine from Symfony. 4 | 5 | ![Header](images/logos/social.png) 6 | 7 | ## Introduction 8 | 9 | Hello 👋, 10 | 11 | My name is Alexander Schranz ([alex_s_](https://twitter.com/alex_s_)) and I'm fulltime Webdeveloper 12 | working on the [SULU CMS](https://sulu.io/?utm_source=github&utm_medium=repository&utm_campaign=alex-twig-for-react-devs) 13 | and did based on that created a lot of websites. 14 | 15 | With this article I wanted to make it easier for React Developers 16 | getting into the Twig Syntax. 17 | 18 | Every Section will first Provide the `React JS` ⚛️ code and then the `Twig` 🌱 19 | followed by explanation about the difference between them. 20 | 21 | At the start I want to mention here that `Twig` was inspired a lot by 22 | [Jinja2 Template Engine](https://github.com/pallets/jinja) 23 | developed for the Django Python Framework, but was improved 24 | over the years with other new features by the Symfony Team. 25 | 26 | Before continue with the overview the best comparison is 27 | what `JSX` is for `React` (JS), `Twig` is for `Symfony` (PHP). 28 | So all Business Logic should live outside of it. 29 | 30 | **Table of Contents** 31 | 32 | - [Outputting a variable](#outputting-a-variable) 33 | - [Outputting raw HTML](#outputting-raw-html) 34 | - [If statements](#if-statements) 35 | - [Loops](#loops) 36 | - [Base Template](#base-template) 37 | - [Providing props to templates](#providing-props-to-templates) 38 | - [Include and override parts](#include-and-override-parts) 39 | - [Dynamic Include](#dynamic-include) 40 | - [Conclusion](#conclusion) 41 | 42 | ## Outputting a variable 43 | 44 | In the first example we will to the most simpliest thing just 45 | outputting a variable we did create in our component / template. 46 | 47 | ### Outputting a variable (React JS ⚛️) 48 | 49 | ```js 50 | // src/views/My.js 51 | 52 | import React from 'react'; 53 | 54 | class My extends React.Component { 55 | render() { 56 | const title = 'My Title'; 57 | 58 | return

{title}

; 59 | } 60 | } 61 | ``` 62 | 63 | ### Outputting a variable (Twig 🌱) 64 | 65 | ```twig 66 | {# templates/pages/my.html.twig #} 67 | 68 | {% set title = 'My Title' %} 69 | 70 |

{{ title }}

71 | ``` 72 | 73 | ### Outputting a variable (Explanation) 74 | 75 | Outputting a variable in Twig and JSX is very similar instead 76 | of `{variable}` you use `{{ variable }}`. The spaces are optional 77 | but recommended by the [Twig coding standards](https://twig.symfony.com/doc/3.x/coding_standards.html). 78 | 79 | ## Outputting raw HTML 80 | 81 | There are cases where you use CKEditor or other rich text editors 82 | to create content which will provide you raw HTML code you would 83 | like to render in this case you want not that your variable you 84 | want to outputted does get escaped. 85 | 86 | ### Outputting raw HTML (React JS ⚛️) 87 | 88 | ```js 89 | // src/views/My.js 90 | 91 | import React from 'react'; 92 | 93 | class My extends React.Component { 94 | render() { 95 | const someHtml = '

My Text editor content

'; 96 | 97 | return
; 98 | } 99 | } 100 | ``` 101 | 102 | ### Outputting raw HTML (Twig 🌱) 103 | 104 | ```twig 105 | {# templates/pages/my.html.twig #} 106 | 107 | {% set someHtml = '

My Text editor content

' %} 108 | 109 |
110 | {{ someHtml|raw }} 111 |
112 | ``` 113 | 114 | ### Outputting raw HTML (Explanation) 115 | 116 | Both (`React JS` and `Twig`) escape variables by default so no HTML 117 | injected without a call of an additional prop or filter. 118 | 119 | There are in Twig other ways to disable `autoescape` over a whole 120 | content. See for this the [official Twig Docs](https://twig.symfony.com/doc/3.x/api.html#escaper-extension). 121 | 122 | I personally recommend only using the `|raw` filter todo this kind 123 | of things. 124 | 125 | ## If statements 126 | 127 | ### If statements (React JS ⚛️) 128 | 129 | ```js 130 | // src/views/My.js 131 | 132 | import React from 'react'; 133 | 134 | class My extends React.Component { 135 | render() { 136 | var title = 'Test' ; 137 | const list = ['Test', 'Test 2']; 138 | 139 | // Basic equal if statement 140 | if (title == 'test') { 141 | title = 'Basic equal if statement'; 142 | // Not equal if statement 143 | } else if (title != 'test') { 144 | title = 'Not equal if statement'; 145 | // Typesafe if statemenet 146 | } else if (title === false) { 147 | title = 'Typesafe if statemenet'; 148 | // Typesafe not if statemenet 149 | } else if (title !== false) { 150 | title = 'Typesafe not if statemenet'; 151 | // In array if statemenet 152 | } else if (list.includes(title)) { 153 | title = 'In array if statemenet'; 154 | // Not in array if statemenet 155 | } else if (!list.includes(title)) { 156 | title = 'Not in array if statemenet'; 157 | // Greater if statement 158 | } else if (title > 10) { 159 | title = 'Greater if statement'; 160 | // And if statement 161 | } else if (title > 10 && title < 10) { 162 | title = 'And if statement'; 163 | // Or if statement 164 | } else if (title > 10 || title != 0) { 165 | title = 'Or if statement'; 166 | // Else if statement 167 | } else { 168 | title = 'Else if statement'; 169 | } 170 | 171 | return
172 | {title} 173 | 174 | {title ? title : 'Other'} 175 |
; 176 | } 177 | } 178 | ``` 179 | 180 | ### If statements (Twig 🌱) 181 | 182 | ```twig 183 | {# templates/pages/my.html.twig #} 184 | 185 | {% set title = 'Test' %} 186 | {% set list = ['Test', 'Test 2'] %} 187 | 188 | {# Basic equal if statement #} 189 | {% if title == 'test' %} 190 | {% set title = 'Basic equal if statement' %} 191 | {# Basic not equal if statement #} 192 | {% elseif title != 'test' %} 193 | {% set title = 'Basic not equal if statement' %} 194 | {# Typesafe if statement #} 195 | {% elseif title same as(false) %} {# Very uncommon in Twig todo typesafe checks #} 196 | {% set title = 'Typesafe if statement' %} 197 | {# Typesafe not if statement #} 198 | {% elseif title not same as(false) %} {# Very uncommon in Twig todo typesafe checks #} 199 | {% set title = 'Typesafe not if statement' %} 200 | {# In array if statement #} 201 | {% elseif title in list %} 202 | {% set title = 'In array if statement' %} 203 | {# Not in array if statement #} 204 | {% elseif title not in list %} 205 | {% set title = 'Not in array if statement' %} 206 | {# Greater if statement #} 207 | {% elseif title > 10 %} 208 | {% set title = 'Greater if statement' %} 209 | {# And if statement #} 210 | {% elseif title > 10 and title < 10 %} 211 | {% set title = 'And if statement' %} 212 | {# Or if statement #} 213 | {% elseif title > 10 or title != 0 %} 214 | {% set title = 'Or if statement' %} 215 | {# Else if statement #} 216 | {% else %} 217 | {% set title = 'Else if statement' %} 218 | {% endif %} 219 | 220 |
221 | {{ title }} 222 | 223 | {{ title ?: 'Other' }} 224 |
225 | ``` 226 | 227 | ### If statements (Explanation) 228 | 229 | For if statement the [if tag](https://twig.symfony.com/doc/3.x/tags/if.html) 230 | is used. Where in JavaScript it is very common to have 231 | typesafe check with `===`. This is very uncommon in twig 232 | because Twig is really focused being a template engine and 233 | not a language where you should build business logic. 234 | 235 | It is still possible in Twig doing a typesafe check using 236 | the [same as test](https://twig.symfony.com/doc/3.x/tests/sameas.html). 237 | 238 | Twig like PHP supports also the ternary operator (`?:`) and the 239 | null-coalescing operator (`??`). Read more about them in the 240 | [Twig operators documentation](https://twig.symfony.com/doc/3.x/templates.html#other-operators). 241 | 242 | ## Loops 243 | 244 | ### Loops (React JS ⚛️) 245 | 246 | ```js 247 | // src/views/My.js 248 | 249 | import React from 'react'; 250 | 251 | class My extends React.Component { 252 | render() { 253 | const list = [{ 254 | title: 'List Item 1', 255 | }, { 256 | title: 'List Item 2', 257 | }]; 258 | 259 | return ; 266 | } 267 | } 268 | ``` 269 | 270 | ### Loops (Twig 🌱) 271 | 272 | ```twig 273 | {# templates/pages/my.html.twig #} 274 | 275 | {% set list = [ 276 | { 277 | title: 'List Item 1', 278 | }, 279 | { 280 | title: 'List Item 2', 281 | } 282 | ] %} 283 | 284 | 289 | ``` 290 | 291 | ### Loops (Explanation) 292 | 293 | Where in `JavaScript` different way of looping exist in twig 294 | you will work with the [for](https://twig.symfony.com/doc/3.x/tags/for.html) 295 | to loop over variables. 296 | 297 | Twig here supports some range function to make developing of 298 | templates easier for example: 299 | 300 | ```twig 301 | {# Output numbers from 0 - 10 #} 302 | {% for i in 0..10 %} 303 | * {{ i }} 304 | {% endfor %} 305 | 306 | {# Output letters from a-z #} 307 | {% for i in 'a'..'z' %} 308 | * {{ i }} 309 | {% endfor %} 310 | 311 | {# Output letters from A-Z #} 312 | {% for letter in 'a'|upper..'z'|upper %} 313 | * {{ letter }} 314 | {% endfor %} 315 | ``` 316 | 317 | By default inside the `for` loop Twig provide some magic variable 318 | called `loop` which will provide you the following data: 319 | 320 | ```twig 321 | {% for i in 0..10 %} 322 | {{ loop.index }} {# index beginning with 1 #} 323 | {{ loop.index0 }} {# index beginning with 0 #} 324 | {{ loop.revindex }} {# reverted index #} 325 | {{ loop.revindex0 }} {# reverted index0 #} 326 | {{ loop.first }} {# boolean flag if its the first item of this loop #} 327 | {{ loop.last }} {# boolean flag if its the last item of this loop #} 328 | {{ loop.length }} {# the number of items of this loop #} 329 | {{ loop.parent }} {# access the parent loop variable if using nested loops #} 330 | {% endfor %} 331 | ``` 332 | 333 | Where in `React JS` you maybe need a `counter` or accessing the length 334 | in `Twig` the `loop` variable is available inside every loop and make 335 | it easy to add CSS classes to first or last items of a loop. 336 | 337 | See here also the Twig documentation about the [for](https://twig.symfony.com/doc/3.x/tags/for.html) 338 | tag. 339 | 340 | As `React JS` has access to all `JavaScript` array available function 341 | Twig also has it tricks to make outputting of html very simple. 342 | 343 | So for example let see that we have variable which content is the 344 | following: 345 | 346 | ```js 347 | ["1", "2", "3", "4", "5"] 348 | ``` 349 | 350 | and we want to render the following HTML: 351 | 352 | ```html 353 |
354 |
355 |
356 | 1 357 |
358 | 359 |
360 | 2 361 |
362 | 363 |
364 | 3 365 |
366 |
367 | 368 |
369 |
370 | 4 371 |
372 | 373 |
374 | 5 375 |
376 | 377 |
378 | 379 |
380 |
381 |
382 | ``` 383 | 384 | This can in Twig easily be rendered by the `batch` function 385 | the following way: 386 | 387 | ```twig 388 |
389 | {% for row in list|batch(3, '') %} 390 |
391 | {% for item in row %} 392 |
393 | {{ item }} 394 |
395 | {% endfor %} 396 |
397 | {% endfor %} 398 |
399 | ``` 400 | 401 | Read here also the offical documentation about the 402 | [Twig batch function](https://twig.symfony.com/doc/3.x/filters/batch.html). 403 | 404 | ## Base Template 405 | 406 | The first you mostly create when building a website is a base 407 | template or base component which contains all things which your 408 | templates have in common like Navigation, Footer, Header. 409 | 410 | ### Base Template (React JS ⚛️) 411 | 412 | In react for rendering anything we first need to create a 413 | `index.html` where the react application is started which also 414 | contains the minimal required html for a page. 415 | 416 | ```html 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | ``` 428 | 429 | In react a base template could look like the following. 430 | 431 | ```js 432 | // src/views/Base.js 433 | 434 | import React from 'react'; 435 | import Head from 'next/head' 436 | import Navigation from './components/Navigation'; 437 | import Header from './components/Header'; 438 | import Footer from './components/Footer'; 439 | 440 | class Base extends React.Component { 441 | render() { 442 | const { 443 | header 444 | } = this.props; 445 | 446 | const headerComponent = header ? header :
; 447 | 448 | return
449 | 450 | {this.props.title} 451 | 452 | 453 | 454 | 455 | 456 |
457 | 458 |
459 | {this.props.children} 460 |
461 | 462 |
463 |
; 464 | } 465 | } 466 | ``` 467 | 468 | A default template build on top of the base template will look like the following way: 469 | 470 | ```js 471 | // src/views/Default.js 472 | 473 | import React from 'react'; 474 | import Base from './components/Base'; 475 | 476 | class Default extends React.Component { 477 | render() { 478 | const title = 'Some Title'; 479 | 480 | return 481 |

{title}

482 | ; 483 | } 484 | } 485 | ``` 486 | 487 | A homepage template build on top of the base template could look this way 488 | which will override the Header component: 489 | 490 | ```js 491 | // src/views/Homepage.js 492 | 493 | import React from 'react'; 494 | import Base from './components/Base'; 495 | import HeaderBig from './components/HeaderBig'; 496 | 497 | class Homepage extends React.Component { 498 | render() { 499 | const title = 'Some Title'; 500 | 501 | return }> 503 |

{title}

504 | ; 505 | } 506 | } 507 | ``` 508 | 509 | This way we created a reusable base which can be used in all components again. 510 | 511 | ### Base Template (Twig 🌱) 512 | 513 | To implement the same as we did above we need to create the following 514 | in our Twig structure. As Twig is server rendered by PHP in Symfony 515 | we will create the `base.html.twig` which will contain the html which 516 | was in react rendered by `index.html` and `Base.js`. 517 | 518 | ```twig 519 | {# templates/base.html.twig #} 520 | 521 | 522 | {title} 523 | 524 | 525 | 526 | {% include 'include/navigation.html.twig' %} 527 | 528 | {% block header %} 529 | {% include 'include/header.html.twig' %} 530 | {% endblock %} 531 | 532 | {% block content %}{% endblock %} 533 | 534 | {% include 'include/footer.html.twig' %} 535 | 536 | 537 | ``` 538 | 539 | And now we create the 2 templates file which use this `base.html.twig` file. 540 | 541 | ```twig 542 | {# templates/pages/default.html.twig #} 543 | {% extends 'base.html.twig' %} 544 | 545 | {% block content %} 546 |

{title}

547 | {% endblock %} 548 | ``` 549 | 550 | We will also create the `homepage.html.twig` with a custom header. 551 | 552 | ```twig 553 | {# templates/pages/homepage.html.twig #} 554 | {% extends 'base.html.twig' %} 555 | 556 | {% block header %} 557 | {% include 'include/header-big.html.twig' %} 558 | {% endblock %} 559 | 560 | {% block content %} 561 |

{title}

562 | {% endblock %} 563 | ``` 564 | 565 | ### Base Template (Explanation) 566 | 567 | As you see in this example the `extends` will say from which template 568 | they should inherit and with the `block` feature of Twig we can then 569 | provide content or override the defined `blocks` like `header` and 570 | `content` in our Twig template. 571 | 572 | Instead of importing a components like its done react we will use 573 | in Twig the `include` block/method of Twig to render the content of another twig 574 | template. 575 | 576 | Has in this case a look at the [extends tag documentation](https://twig.symfony.com/doc/3.x/tags/extends.html) 577 | and [include tag documentation](https://twig.symfony.com/doc/3.x/tags/include.html) 578 | from the official [Twig Documentation](https://twig.symfony.com/). 579 | 580 | ## Providing props to templates 581 | 582 | ### Providing props to templates (React JS ⚛️) 583 | 584 | ```js 585 | // src/views/Parent.js 586 | 587 | import React from 'react'; 588 | import Child from './components/Child'; 589 | 590 | class Parent extends React.Component { 591 | render() { 592 | const someTitle = 'Some Title'; 593 | 594 | return ; 595 | } 596 | } 597 | ``` 598 | 599 | ### Providing props to templates (Twig 🌱) 600 | 601 | ```twig 602 | {% set title = 'Some Title' %} 603 | 604 | {% include 'includes/child.html.twig' %} 605 | ``` 606 | 607 | ### Providing props to templates (Explanation) 608 | 609 | Where in `React JS` the only way to give properties from a parent 610 | to a child is giving them as a `props` in `Twig` by default a 611 | included `Template` has access to all variables which were defined 612 | or are accessible in the parent template. 613 | 614 | Twig still provide ways to provide properties to included template 615 | this way: 616 | 617 | ```twig 618 | {% include 'includes/child.html.twig' with { title: 'Some Title' } %} 619 | ``` 620 | 621 | If you don't want that the included template can access the variables 622 | defined in the parent template this can be avoided using the `only` 623 | keyword: 624 | 625 | ```twig 626 | {% set someTitle = 'Some Title' %} 627 | 628 | {% include 'includes/child.html.twig' with { title: someTitle } only %} 629 | ``` 630 | 631 | Read more about the include tag in the official [Twig Include Documentation](https://twig.symfony.com/doc/3.x/tags/include.html). 632 | 633 | ## Include and override parts 634 | 635 | Sometimes you need to include and override a specific template 636 | or Component. In this section we will look at this parts. 637 | 638 | ### Include and override parts (React JS ⚛️) 639 | 640 | ```js 641 | // src/views/Parent.js 642 | 643 | import React from 'react'; 644 | import Child from './components/Child'; 645 | import CustomHeader from './components/CustomHeader'; 646 | import CustomFooter from './components/CustomFooter'; 647 | 648 | class Parent extends React.Component { 649 | render() { 650 | return } 652 | footer={}> 653 | {this.props.children} 654 | ; 655 | } 656 | } 657 | ``` 658 | 659 | ```js 660 | // src/component/Child.js 661 | 662 | import React from 'react'; 663 | import Child from './components/Child'; 664 | import Header from './components/Header'; 665 | import Footer from './components/Footer'; 666 | 667 | class Parent extends React.Component { 668 | render() { 669 | const HeaderComponent = this.props.header ? this.props.header :
; 670 | const FooterComponent = this.props.footer ? this.props.footer :