gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
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!
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.
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.
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.
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.
Hey @Dominik!
This is super weird behavior. It's even STRANGER because I've never heard of an issue like this... until 3 days ago when someone else had something that sounds VERY similar. I'm not sure if this will be relevant to you, but I hope it will be - https://github.com/symfony/webpack-encore/issues/1209#issuecomment-1560650833
Cheers!
Hello ! I followed the tutorial but it looks like I have a javascript error in my browser's console
Uncaught TypeError: Cannot read property 'render' of undefined
at Object../assets/js/react/form/registration.tsx (registration.tsx:14)
at __webpack_require__ (bootstrap:79)
at checkDeferredModules (bootstrap:45)
at Array.webpackJsonpCallback [as push] (bootstrap:32)
at registration_form.js:1
my code is
import React = require('react');
import ReactDOM from 'react-dom';
class RegistrationForm extends React.Component {
render() {
return (
<div>
<p>test</p>
</div>
);
}
}
ReactDOM.render(<RegistrationForm/>, document.getElementById('registration-form'));
I may have done something wrong but I'm not sure of what, I've reviewed the actions made in the tutorials and I don't think to have missed anything
Hey Infrandomness,
Hm, could you follow the next steps:
$ rm -rf node_modules/
$ yarn install
$ yarn run encore dev --watch
I.e. just basically re-install all the Node dependencies and re-build the assets with Encore and try again. Do you see the same error? Btw, did you download the course code and bootstrap the project from start/ folder? Or do you apply changes on your own project?
Cheers!
HI ! Thanks for your answers !
First of all, I successfully fixed my project.
As I wrote my components in typescript, I forgot to install the @types packages of both react and react-dom
To answer your questions :
I apply the changes to my own project as I watch the tutorials
Hi guys,
If you have separate pages on your app, would you have to create separate React apps? For example PostListApp, PostShowApp?
Hey Shaun T.!
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!
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
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!
Stupid question time. We already have a class RepLogApp in our RepLogApp.js file . Why is nothing complaining about the duplicate name?
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!
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.
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!
// composer.json
{
"require": {
"php": "^7.2.0",
"ext-iconv": "*",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"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.17.6
"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
}
}
// package.json
{
"dependencies": {
"@babel/plugin-proposal-object-rest-spread": "^7.12.1" // 7.12.1
},
"devDependencies": {
"@babel/preset-react": "^7.0.0", // 7.12.5
"@symfony/webpack-encore": "^0.26.0", // 0.26.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
"bootstrap": "3", // 3.3.7
"copy-webpack-plugin": "^4.4.1", // 4.5.1
"core-js": "2", // 1.2.7
"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
"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": "^1.29.0", // 1.29.0
"sass-loader": "^7.0.0", // 7.3.1
"sweetalert2": "^7.11.0", // 7.22.0
"uuid": "^3.2.1", // 3.4.0
"webpack-notifier": "^1.5.1", // 1.6.0
"whatwg-fetch": "^2.0.4" // 2.0.4
}
}
Hello Guys, im trying to execute the integration between Symfony 6 and React 18 but once I ran the npm run watch the encore compile the JS files twice and after the 3 modification it keep compiling without error BUT it does not update the files int the public folder anymore. it a fresh new project. is someone facing this issue? I already tryed react 17 and 18 the result is the same.
and my twig file