gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
So far, the component is 100% static. What I mean is, there are no variables at all. No matter what, each time we render RepLogApp
, we will get the exact same result.
I said earlier that a React component is kind of like a PHP class. Well, when we instantiate a class in PHP, we can pass in different arguments to make different instances of the same class behave in different ways.
And... yea! We can do the exact same thing here: we can pass variables to a component when it's rendered, and use those in the render()
method.
But, first, a quick note: when you use the JSX syntax for a component, React instantiates a new instance of our class. Yep, we can actually render multiple RepLogApp
components on the page, and each of them will be a separate object. with separate data.
It turns out, that's hugely powerful. Suppose that we want to be able to render our RepLogApp
in multiple places on the page at the same time. But, sometimes we want the heart Emoji, but sometimes we don't. To make that possible, we need to be able to pass a flag to RepLogApp
that tells it whether or not to render the heart.
Inside rep_log_react.js
, create a new const shouldShowHeart = true
. I'll also update the render code to use multiple lines, just to keep things clear.
... lines 1 - 4 | |
const shouldShowHeart = true; | |
... lines 6 - 11 |
So, how can we pass this variable into RepLogApp
? By adding what looks like an attribute: withHeart
- I'm just making that name up - equals {shouldShowHeart}
.
... lines 1 - 6 | |
render( | |
<RepLogApp withHeart={shouldShowHeart} />, | |
document.getElementById('lift-stuff-app') | |
); |
Woh. Wait. Something crazy just happened. We are inside of JSX on this line. And, because JSX is like HTML, we know that we could, of course, say something like withHeart="foo"
. That's true, but whenever you're in JSX, if you write {}
, that puts you back into JavaScript mode! Once inside, You can write literally any valid JavaScript: like reference the shouldShowHeart
variable or even add expressions. We'll do this all the time.
Now, I referred to withHeart
as an "attribute". But, in React, this is actually known as a prop
, and its the way that you pass "arguments" or "data" into your component. Inside RepLogApp
, we can access any props that were passed to us via a this.props
property.
Check this out: in render()
create a new variable called heart
and set it to empty quotes. Then, if this.props.withHeart
- referencing the prop we passed in - say heart =
, copy the span
JSX from below, and paste it here.
... lines 1 - 2 | |
export default class RepLogApp extends Component { | |
render() { | |
let heart = ''; | |
if (this.props.withHeart) { | |
heart = <span>❤️</span>; | |
} | |
... lines 9 - 12 | |
} | |
} |
Oh, and notice that when we use this.props.withHeart
, we have an error from ESLint about some missing prop validation. That's just a warning, and we're going to talk about it later. For now, totally ignore it.
Below, I want to break my return
statement onto multiple lines. You can use multiple lines to define JSX, as long as you surround it with parenthesis. I do this a lot for readability.
Finally, instead of the span, we want to print the heart
variable. How? Use {heart}
. Based on the value of the prop, this will print an empty string or a React element.
... lines 1 - 9 | |
return ( | |
<h2>Lift Stuff! {heart}</h2> | |
); | |
... lines 13 - 15 |
Right now, withHeart
is equal to true. So let's see if this work: find your browser and refresh! Yes! We still see the heart! Change shouldShowHeart
to false
and try it again. The heart is gone!
To really show off, change that value back to true, but let's see if we can render RepLogApp
multiple times. Copy the JSX, paste, and set withHeart
to false
.
... lines 1 - 6 | |
render( | |
<RepLogApp withHeart={shouldShowHeart} /> <RepLogApp withHeart={false} />, | |
document.getElementById('lift-stuff-app') | |
); |
But, as soon as we do this, the Webpack build fails! Find your terminal to see what it's complaining about:
Syntax Error: Adjacent JSX elements must be wrapped in an enclosing tag
This is less scary than it sounds. It's not that you can't put components next to each other like this, it just means that there must be just one element all the way at the top of our JSX tree. Each component also needs to follow this rule. And, RepLogApp
already is: it has one top-level element: the h2
.
To put just one element at the top of our element tree, there's a simple fix: add a div
and render both components inside. Oh, and I completely forgot to use {}
around my "false" - false
is JavaScript.
... lines 1 - 6 | |
render( | |
<div> | |
<RepLogApp withHeart={shouldShowHeart} /> | |
<RepLogApp withHeart={false} /> | |
</div>, | |
document.getElementById('lift-stuff-app') | |
); |
Now that Webpack is happy again, go back and refresh! Sweet! Our component is rendered twice: each is its own object with its own data.
Props are just about the most important concept in React, and they will be the key to us creating killer UI's that update dynamically.
Back in rep_log_react.js
, we don't really need two of these components: so go back to just one. And, beautiful!
It's time to build out the rest of our app: first, by moving the table into RepLogApp
.
Hey Micka,
Unfortunately, you can't. See syntax of "const" statement: https://developer.mozilla.o... - it just "%name% = %value%". We're talking about simple variables here with "const"... in your PHP example you're talking about class properties :) And fairly speaking you don't need to typehint "const" because you can set its value only once. If you need a bool const - set a bool value and that's it. You will be 100% sure it's bool further in the code as you can't re-set it because it's a constant.
Cheers!
Hey guys!
So we can pass a prop from rep_log_react.js to RepLogApp, but what's the right way to pass that prop from our twig template to rep_log_react.js?
I did it with this in my controller:
return $this->render('lift/index.html.twig', array(
'leaderboard' => $this->getLeaders($replogRepo, $userRepo),
'shouldShowHeart' => true,
));
This in my template:
<div id="lift-stuff-app" data-should-show-heart={{shouldShowHeart}}></div>
And this in rep_log_react.js:
const shouldShowHeart = document.querySelector('#lift-stuff-app').dataset.shouldShowHeart;
render(
<RepLogApp withHeart={shouldShowHeart}/>,
document.getElementById('lift-stuff-app')
);
That works, but it doesn't look hipster enough to belong in the React world. Is there a better way?
Thanks!
Hey Dan_M!
but it doesn't look hipster enough to belong in the React world
Haha! Well done :). There are actually a bunch of different ways to do this - I was just having this conversation with several people this past week. We do talk about this WAY later in the tutorial - see https://symfonycasts.com/screencast/reactjs/server-pass-props and the chapter before it (for a bit of the setup).
In that chapter, we simply set a global variable. But, other options include - setting data- attributes (like you've done) and creating a special <script type="text/json" id="my-original-data">
and then reading and decoding that JSON. That last one actually came out as the "subtle" winner from my talks this pas week... though they are all approximately the same as far as security goes, etc.
So, long way of saying - your way isn't so un-hipster actually ;). Though you can (and we show this in that other chapter) do some cool stuff where you use the ...spread operator so that you don't need to grab each piece of data and manually do the attribute={var} way of setting things.
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
}
}
Hi guys, can we type the variable ? Like php : public bool isTrue. Doing the same with shouldShowHeart : const bool shouldShowHeart ?