Integrating React and Symfony 4.2.4.

This post will be useful for people who already know what React and Symfony are. The purpose of the HowTo is only to remind how to integrate React Javascript library with Symfony framework to write Ajax applications.

Our sample application will show planets catalogue. The data will be transferred via Ajax request from Symfony controller. Nothing special.

 

Run the following command to create a new symfony project.

composer create-project symfony/website-skeleton SampleProject
cd ./SampleProject

To open the project in browser we will move to ‘SampleProject’ directory and run the Symfony Web Server using ‘./bin/console server:run’. Our sample project will be available on http://127.0.0.1:8000.

Now we will install Webpack Encore Bundle:

composer require encore

After that you will see ‘./webpack.connfig.js’ file and ‘assets’ directory.

To install React, React-Dom and other required components we will use the yarn.

yarn add --dev @babel/preset-react
yarn add react react-dom prop-types react-router-dom reactstrap axios
yarn add @babel/plugin-proposal-class-properties @babel/plugin-transform-runtime

Now let us edit the ./webpack.config.js file and change the content to

var Encore = require('@symfony/webpack-encore');

Encore
    .setOutputPath('public/build/')
    .setPublicPath('/build')
    .addEntry('app', './assets/js/app.js')
    .enableSingleRuntimeChunk()
    .cleanupOutputBeforeBuild()
    .enableBuildNotifications()
    .enableSourceMaps(!Encore.isProduction())
    .enableVersioning(Encore.isProduction())
    .enableReactPreset()

    .configureBabel(function (babelConfig) {
        babelConfig.plugins = [
            "@babel/plugin-proposal-object-rest-spread","@babel/plugin-proposal-class-properties",
            "@babel/plugin-transform-runtime"
        ]
    })
;

module.exports = Encore.getWebpackConfig();

Let us create two controllers: DefaultController and PlanetController.

./bin/console make:controller DefaultController
./bin/console make:controller PlanetController

This command will create two files in ‘./src/Controller/DefaultController.php’ file and appropriate twig templates. The DefaultController.php file will have the following look.

<?php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class DefaultController extends AbstractController
{
    /**
     * @Route("/{reactRouting}", name="home", defaults={"reactRouting": null})
     */
    public function index()
    {
        return $this->render('default/index.html.twig');
    }
}

PlanetController will be the serve the Ajax requests. I have hardcoded the planets json to make the code as simple as possible.

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;

class PlanetController extends AbstractController
{
    /**
     * @Route("/planet/getPlanets", name="planet_getPlanets")
     */
    public function getPlanets()
    {
        $planets = array(
            array(
              'id' => 'mercury',
              'name' => 'Mercury',
              'moons' => 0,
              'diameter' => 4879,
              'distanceFromSun' => 57.9,
              'img' => 'https://solarsystem.nasa.gov/system/feature_items/images/18_mercury_new.png',
              'wikiUrl' => 'https://en.wikipedia.org/wiki/Mercury_(planet)'
            ),
            array(
              'id' => 'venus',
              'name' => 'Venus',
              'moons' => 0,
              'diameter' => 12104,
              'distanceFromSun' => 108.2,
              'img' => 'https://solarsystem.nasa.gov/system/feature_items/images/27_venus_jg.png',
              'wikiUrl' => 'https://en.wikipedia.org/wiki/Venus'
            ),
            array(
              'id' => 'earth',
              'name' => 'Earth',
              'moons' => 1,
              'diameter' => 12756,
              'distanceFromSun' => 149.6,
              'img' => 'https://solarsystem.nasa.gov/system/feature_items/images/17_earth.png',
              'wikiUrl' => 'https://en.wikipedia.org/wiki/Earth'
            ),
            array(
              'id' => 'mars',
              'name' => 'Mars',
              'moons' => 0,
              'diameter' => 6792,
              'distanceFromSun' => 227.9,
              'img' => 'https://solarsystem.nasa.gov/system/feature_items/images/19_mars.png',
              'wikiUrl' => 'https://en.wikipedia.org/wiki/Mars'
            ),
            array(
              'id' => 'jupiter',
              'name' => 'Jupiter',
              'moons' => 67,
              'diameter' => 142984,
              'distanceFromSun' => 778.6,
              'img' => 'https://solarsystem.nasa.gov/system/feature_items/images/16_jupiter_new.png',
              'wikiUrl' => 'https://en.wikipedia.org/wiki/Jupiter'
            ),
            array(
              'id' => 'saturn',
              'name' => 'Saturn',
              'moons' => 62,
              'diameter' => 120536,
              'distanceFromSun' => 1433.5,
              'img' => 'https://solarsystem.nasa.gov/system/feature_items/images/28_saturn.png',
              'wikiUrl' => 'https://en.wikipedia.org/wiki/Saturn'
            ),
            array(
              'id' => 'uranus',
              'name' => 'Uranus',
              'moons' => 27,
              'diameter' => 51118,
              'distanceFromSun' => 2872.5,
              'img' => 'https://solarsystem.nasa.gov/system/feature_items/images/29_uranus.png',
              'wikiUrl' => 'https://en.wikipedia.org/wiki/Uranus'
            ),
            array(
              'id' => 'neptune',
              'name' => 'Neptune',
              'moons' => 14,
              'diameter' => 49528,
              'distanceFromSun' => 4495.1,
              'img' => 'https://solarsystem.nasa.gov/system/feature_items/images/30_neptune.png',
              'wikiUrl' => 'https://en.wikipedia.org/wiki/Neptune'
            ),
          );
        return new JsonResponse(array(
            "planets" => $planets
        ));
    }
}

Now let us change the content of ‘base.html.twig’ and ‘index.html.twig’ files.

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>{% block title %}Welcome!{% endblock %}</title>
    {% block stylesheets %}{% endblock %}
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
              crossorigin="anonymous">
 </head>
<body>
{% block body %}{% endblock %}
{% block javascripts %}
    {{ encore_entry_script_tags('app') }}
{% endblock %}
</body>
</html>

Here I have included bootstrap.min.css to style react components. In the ‘index.html.twig’ I have inserted the container div element with ‘root’ id.

{% extends 'base.html.twig' %}
    
{% block title %}Table of planetary statistics{% endblock %}
    
{% block body %}
<div>
    <center><h1>Table of planetary statistics</h1></center>
    <div id="root"></div>
</div>
{% endblock %}

Our React components will be placed in assets directory. The content of this directory will be built and placed in public directory.

Our application will consist of two components ‘./assets/app.js’ and ‘./assets/Components/Items.js’.

import React from 'react';
import ReactDOM from 'react-dom';

import Items from './Components/Items';


class App extends React.Component {
    constructor() {
        super();

        this.state = {
            planets: []
        };
    }

    componentDidMount() {
        fetch('/planet/getPlanets')
            .then(response => response.json())
            .then(planets => {
                this.setState({
                    planets: planets.planets
                });
            });
    }

    render() {
        return (
            <div className="container">
                <div className="row">
                    {this.state.planets.map(
                        ({ id, img, name, moons, diameter, distanceFromSun, wikiUrl }) => (
                            <Items
                                key={id}
                                img={img}
                                name={name}
                                moons={moons}
                                diameter={diameter}
                                distanceFromSun={distanceFromSun}
                                wikiUrl={wikiUrl}
                            >
                            </Items>

                        )
                    )}                
                </div>
            </div>
        );
    }
}

ReactDOM.render(<App />, document.getElementById('root'));
import React from 'react';

class Item extends React.Component {
    constructor() {
        super();
        this.planetImageStyle = {
            background: '#000 url("https://solarsystem.nasa.gov/assets/footer_bg.jpg") top center no-repeat'
        };

        this.state = {};
    }

    render() {
        return (
            <div className="card text-center" style={{margin: 5}}>
                <img className="card-img-top" src={this.props.img} style={this.planetImageStyle}/>
                <div className="card-body">    
                    <h3 className="card-title">{this.props.name}</h3>
                    <ul className="list-group">
                        <li className="list-group-item"><b>Moons: </b> {this.props.moons}</li>
                        <li className="list-group-item"><b>Diameter: </b> {this.props.diameter} km</li>
                        <li className="list-group-item"><b>Average distance from Sun: </b> {this.props.distanceFromSun} Mio km</li>
                    </ul>
                    <a href={this.props.wikiUrl} target="blank" className="btn btn-primary" style={{marginTop: 10}}>{this.props.name} in Wikipedia</a>
                </div>
            </div>
        );
    }
}

export default Item;

To run the dev environment and auto-build the project we will open two shells to run Symfony server:

./bin/console server:run

and the yarn encore:

yarn encore dev --watch