Buy

JavaScript for PHP Geeks: ReactJS (with Symfony)

0%
Buy

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!

  • 2018-09-17 weaverryan

    Hey Shaun!

    Great question! Yes. Well, probably yes. In your example, these indeed sound like 2 totally separate "apps". So yes, I would set it up like you are (the cool thing is if there is some shared logic, you can extract that into components that each app uses). If you're building a traditional app, each page would have a different webpack "entry" that would initialize the correct "app". If you're building a single-page app, you'll likely use React router (a topic we didn't have time for). React router is a pretty simple idea: based on the URL, it "renders" different components. For example, you might configure /posts to render PostListApp and /posts/5 to render PostShowApp.

    Cheers!

  • 2018-09-16 Shaun

    Hi guys,

    If you have separate pages on your app, would you have to create separate React apps? For example PostListApp, PostShowApp?

  • 2018-07-02 Victor Bocharsky

    Hey Amy,

    Yeah, agree, and you should avoid duplicating class names in your project even if it works because you never import them both in one file. That's because we were doing refactoring step by step, but probably in a real project would be cool to change that legacy class name to something like LegacyRepLogApp etc. It requires a bit more work, but will be clearer.

    Cheers!

  • 2018-07-02 Victor Bocharsky

    Hey Jovan,

    You need to restart webpack encore only if you change webpack.config.js, i.e. when add a new entry point. But if you just move a class into a separate file that you just want to import in an existent and known for webpack encore entry point - it will work, so you don't need to restart it.

    Cheers!

  • 2018-06-30 Jovan Perović

    If I vaguely recall, this was mentioned in another Encore course, **but**, once we externalize the RepLogApp's code into separate file, webpack does not pick it up automatically :) We need to restart it in order to keep things smooth and not be slapped by "RepLogApp is undefined" error :)

    Version info:
    └─ webpack@3.12.0
    └─ @symfony/webpack-encore@0.19.0

  • 2018-06-27 Amy

    Actually, I'm asking about js/Components/RepLogApp.js (which is powering the legacy app) and /js/RepLog/RepLogApp.js which is powering the new version. Both have the exact same class name. I see now that the import is directory based and therefore each entry point is able to use only the code it knows about, but at first it seems like it would cause all sorts of havoc.

  • 2018-06-27 Victor Bocharsky

    Hey Amy,

    Because this is JavaScript? :p To be serious, probably because we moved that RepLogApp class into its own file assets/js/RepLog/RepLogApp.js. I mean, after we copied it from assets/js/rep_log_react.js and pasted into assets/js/RepLog/RepLogApp.js - we completely removed it from assets/js/rep_log_react.js and import RepLogApp in this file from assets/js/RepLog/RepLogApp.js. Does it makes sense to you? Oh, and yes, that's because of the scope, each file have its own scope, so I can declare variables, classes, etc. with the same names but in different files.

    Cheers!

  • 2018-06-26 Amy

    Stupid question time. We already have a class RepLogApp in our RepLogApp.js file . Why is nothing complaining about the duplicate name?