React Components

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

It works like this: we create React element objects and ask React to render them. But, React has another important object: a Component. Ooooooooo.

It looks like this: create a class called RepLogApp and extend something called React.Component. A component only needs to have one method render(). Inside, return whatever element objects you want. In this case I'm going to copy my JSX from above and, here, say return and paste.

... lines 1 - 3
class RepLogApp extends React.Component {
render() {
return <h2>Lift Stuff! <span>❤️</span></h2>;
}
}
... lines 9 - 12

This is a React component. Below, I'm going to use console.log() and then treat my RepLogApp as if it were an element: <RepLogApp />.

... lines 1 - 9
console.log(<RepLogApp />);
... lines 11 - 12

Finally, below, instead of rendering an element, we can render the component with that same JSX syntax: <RepLogApp />.

... lines 1 - 10
ReactDom.render(<RepLogApp />, document.getElementById('lift-stuff-app'));

Ok, go back and refresh! Awesome! We get the exact same thing as before! And, check out the console! The component becomes a React element!

The What & Why of Components

This React component concept is something we're going to use a lot. But, I don't want to make it seem too important: because it's a super simple concept. In PHP, we use classes as a way to group code together and give that code a name that makes sense. If we organize 50 lines of code into a class called "Mailer", it becomes pretty obvious what that code does... it spams people! I mean, it emails valued customers!

React components allow you to do the same thing for the UI: group elements together and give them a name. In this case, we've organized our h2 and span React elements into a React component called RepLogApp. React components are sort of a named container for elements.

By the way, React components do have one rule: their names must start with a capital letter. Actually, this rule is there to help JSX: if we tried using a <repLogApp /> component with a lowercase "r", JSX would actually think we wanted to create some new hipster repLogApp HTML element, just like how a <div> becomes a <div>. By starting the component name with a capital letter, JSX realizes we're referring to our component class, not some hipster HTML element with that name.

Making our Imports more Hipster

Anyways, a few minor housekeeping things. Notice that Component is a property on the React object. The way we have things now is fine. But, commonly, you'll see React imported like this: import React then { Component } from react. Thanks to this, you can just extend Component.

import React, { Component } from 'react';
... lines 2 - 3
class RepLogApp extends Component {
... lines 5 - 7
}
... lines 9 - 12

This is pretty much just a style thing. And... honestly... it's one of the things that can make React frustrating. What I mean is, React developers like to use a lot of the newer, fancier ES6 syntaxes. In this case, the react module exports an object that has a Component property. This syntax is "object destructuring": it grabs the Component key from the object and assigns it to a new Component variable. Really, this syntax is not that advanced, and actually, we're going to use it a lot. But, this is one of the challenges with React: you may not be confused by React, you may be confused by a fancy syntax used in a React app. And we definitely don't want that!

We can do the same thing with react-dom. Because, notice, we're only using the render key. So instead of importing all of react-dom, import { render } from react-dom. Below, use the render() function directly.

... line 1
import { render } from 'react-dom';
... lines 3 - 10
render(<RepLogApp />, document.getElementById('lift-stuff-app'));

This change is a little bit more important because Webpack should be smart enough to perform something called "tree shaking". That's not because Webpack hates nature, that's just a fancy way of saying that Webpack will realize that we only need the render() function from react-dom: not the whole module. And so, it will only include the code needed for render in our final JavaScript file.

Anyways, these are just fancier ways to import exactly what we already had.

Oh, but, notice: it looks like the React variable is now an unused import. What I mean is, we don't ever use that variable. So, couldn't we just remove it and only import Components?

Actually, no! Remember: the JSX code is transformed into React.createElement(). So, strangely enough, we are still using the React variable, even though it doesn't look like it. Sneaky React.

To make sure we haven't broken anything... yet, go back and refresh. All good.

One Component Per File

Just like in PHP, we're going to follow a pattern where each React component class lives in its own file. In the assets/js directory, create a new RepLog directory: this will hold all of the code for our React app. Inside, create a new file called RepLogApp. Copy our entire component class into that file.

import React, { Component } from 'react';
class RepLogApp extends Component {
render() {
return <h2>Lift Stuff! <span>❤️</span></h2>;
}
}

Woh. Something weird just happened. Did you see it? We only copied the RepLogApp class. But when we pasted, PhpStorm auto-magically added the import for us! Thanks PhpStorm! Gold star!

But, check out this error:

ESLint: React must be in scope when using JSX.

Oh, that's what we just talked about! This is one of those warnings that comes from ESLint. Update the import to also import React.

Now, to make this class available to other files, use export default class RepLogApp.

import React, { Component } from 'react';
... line 2
export default class RepLogApp extends Component {
... lines 4 - 6
}

Back in rep_log_react.js, delete the class and, instead, import RepLogApp from ./RepLog/RepLogApp. Oh, and it's not too important, but we're actually not using the Component import anymore. So, trash it.

import React from 'react';
... line 2
import RepLogApp from './RepLog/RepLogApp';
... lines 4 - 7

Awesome! Our code is a bit more organized! And when we refresh, it's not broken, which is always my favorite.

The Entry - Component Structure

And actually, this is an important moment because we've just established a basic structure for pretty much any React app. First, we have the entry file - rep_log_react.js - and it has just one job: render our top level React component. In this case, it renders RepLogApp. That's the only file that it needs to render because eventually, the RepLogApp component will contain our entire app.

So the structure is: the one entry file renders the one top-level React component, and it returns all the elements we need from its render() method.

And, that's our next job: to build out the rest of the app in RepLogApp. But first, we need to talk about a super-duper important concept called props.

Leave a comment!

This course uses Symfony 4, but as this is a JavaScript course, all the concepts apply fine to Symfony 5. Have fun!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "ext-iconv": "*",
        "doctrine/doctrine-bundle": "^1.6", // 1.9.1
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.3
        "doctrine/doctrine-fixtures-bundle": "~3.0", // 3.0.2
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.3.1
        "doctrine/orm": "^2.5", // v2.7.2
        "friendsofsymfony/jsrouting-bundle": "^2.2", // 2.2.0
        "friendsofsymfony/user-bundle": "dev-master#4125505ba6eba82ddf944378a3d636081c06da0c", // dev-master
        "sensio/framework-extra-bundle": "^5.1", // v5.2.0
        "symfony/asset": "^4.0", // v4.1.4
        "symfony/console": "^4.0", // v4.1.4
        "symfony/flex": "^1.0", // v1.6.2
        "symfony/form": "^4.0", // v4.1.4
        "symfony/framework-bundle": "^4.0", // v4.1.4
        "symfony/lts": "^4@dev", // dev-master
        "symfony/monolog-bundle": "^3.1", // v3.3.0
        "symfony/polyfill-apcu": "^1.0", // v1.9.0
        "symfony/serializer-pack": "^1.0", // v1.0.1
        "symfony/swiftmailer-bundle": "^3.1", // v3.2.3
        "symfony/twig-bundle": "^4.0", // v4.1.4
        "symfony/validator": "^4.0", // v4.1.4
        "symfony/yaml": "^4.0", // v4.1.4
        "twig/twig": "2.10.*" // v2.10.0
    },
    "require-dev": {
        "symfony/debug-pack": "^1.0", // v1.0.6
        "symfony/dotenv": "^4.0", // v4.1.4
        "symfony/maker-bundle": "^1.5", // v1.5.0
        "symfony/phpunit-bridge": "^4.0", // v4.1.4
        "symfony/web-server-bundle": "^4.0" // v4.1.4
    }
}

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@symfony/webpack-encore": "^0.19.0", // 0.19.0
        "babel-plugin-transform-object-rest-spread": "^6.26.0", // 6.26.0
        "babel-plugin-transform-react-remove-prop-types": "^0.4.13", // 0.4.13
        "babel-preset-react": "^6.24.1", // 6.24.1
        "bootstrap": "3", // 3.3.7
        "copy-webpack-plugin": "^4.4.1", // 4.5.1
        "eslint": "^4.19.1", // 4.19.1
        "eslint-plugin-react": "^7.8.2", // 7.8.2
        "font-awesome": "4", // 4.7.0
        "jquery": "^3.3.1", // 3.3.1
        "node-sass": "^4.7.2", // 4.9.0
        "promise-polyfill": "^8.0.0", // 8.0.0
        "prop-types": "^15.6.1", // 15.6.1
        "react": "^16.3.2", // 16.4.0
        "react-dom": "^16.3.2", // 16.4.0
        "sass-loader": "^6.0.6", // 6.0.7
        "sweetalert2": "^7.11.0", // 7.22.0
        "uuid": "^3.2.1", // 3.2.1
        "webpack-notifier": "^1.5.1", // 1.6.0
        "whatwg-fetch": "^2.0.4" // 0.9.9
    }
}