Buy

JavaScript for PHP Geeks: ReactJS (with Symfony)

0%
Buy

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.

Components are Instantiated

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.

Passing in a Prop

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')
);

The JSX {JavaScript} Syntax

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.

Reading Props from Inside a Component

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!

Rendering a Component Multiple Times

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.

Leave a comment!

  • 2018-11-01 weaverryan

    Hey Dan Meigs!

    > 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/sc... 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!

  • 2018-10-31 Dan Meigs

    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!