Buy

JavaScript for PHP Geeks: ReactJS (with Symfony)

0%
Buy

Passing Server Data to React Props

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

We need to load this itemOptions data dynamically from the server. Copy the options and then find the template for this page: templates/lift/index.html.twig.

At the bottom, you'll find the script that loads our app. Before this, create a new global variable. So, use the window object: window.REP_LOG_APP_PROPS = an object with itemOptions set to our options.

... lines 1 - 63
{% block javascripts %}
... lines 65 - 66
<script>
window.REP_LOG_APP_PROPS = {
itemOptions: [
{id: 'cat', text: 'Cat'},
{id: 'fat_cat', text: 'Big Fat Cat'},
{id: 'laptop', text: 'My Laptop'},
{id: 'coffee_cup', text: 'Coffee Cup'},
{id: 'invalid_item', text: 'Dark Matter'}
]
}
</script>
... lines 78 - 79
{% endblock %}

Now, go back to rep_log_react.js delete the old constant and, below, use window.REP_LOG_APP_PROPS.itemOptions.

... lines 1 - 6
render(
<RepLogApp
... line 9
itemOptions={window.REP_LOG_APP_PROPS.itemOptions}
/>,
... line 12
);

When is it Ok to Use window?

This will work... but... now I have a question. Why couldn't we just copy this code and use it in RepLogCreator instead of passing the prop down all the levels? You could. But, as a best practice, I don't want any of my React components to use variables on the window object. The only place where I want you to feel safe using the global window object is inside of your entry point: it should grab all of the stuff you need, and pass it into your React app.

Like everything, don't live and die by this rule. But, the window object is a global variable. And, just like in PHP, while global variables are easy to use, they make your code harder to debug and understand. Use them in your entry, but that's it.

Spreading all of the Props

Back in the template, I built the REP_LOG_APP_PROPS variable so that we could, in theory, set other props on it. For example, add withHeart: true.

... lines 1 - 66
<script>
window.REP_LOG_APP_PROPS = {
... lines 69 - 75
withHeart: true
}
</script>
... lines 79 - 82

In the entry file, to read this, we could of course use window.REP_LOG_APP_PROPS.withHeart. Or... we can be way cooler! Use spread attributes: ...window.REP_LOG_APP_PROPS.

... lines 1 - 6
render(
<RepLogApp
... line 9
{...window.REP_LOG_APP_PROPS}
/>,
... line 12
);

Suddenly! All of the keys on that object will be passed as props! And this is cool: set shouldShowHeart to false. Hmm: we're now passing withHeart=false... but thanks to the spread prop, we're passing that prop again as true.

... lines 1 - 4
const shouldShowHeart = false;
... lines 6 - 14

When you do this, the last prop always wins. Yep, we do see the heart.

This is a cool way to render a component with initial data that comes from the server.

Dumping JavaScript in Twig

Well, the data isn't quite dynamic yet. Let's finally finish that. Open the form: src/Form/Type/RepLogType.php. The choices options is the data that we want to send to React. Copy RepLog::getThingsYouCanLiftChoices().

Then, go into the controller that renders this page - LiftController and find indexAction(). First, let's dump() that function to see what it looks like.

... lines 1 - 18
public function indexAction(Request $request, RepLogRepository $replogRepo, UserRepository $userRepo)
{
... lines 21 - 22
dump(RepLog::getThingsYouCanLiftChoices());die;
... lines 24 - 27
}
... lines 29 - 56

Move over and refresh! Interesting! It's an array... but it doesn't quite look right. Let's compare this to the structure we want. Ok, each item has an id like cat or fat_cat. That is the value on the array. We also need a text key. My app is using the translator component. The keys on the dumped array need to be run through the translator to be turned into the English text.

Actually, the details aren't important. The point is this: our app does have the data we need... but we need to "tweak" it a little bit to match what our React app is expecting.

To do that, go back to the controller. To save us some tedious work, I'll paste in some code. This code uses the $translator. To get that, add a new controller argument: TranslatorInterface $translator.

... lines 1 - 19
public function indexAction(Request $request, RepLogRepository $replogRepo, UserRepository $userRepo, TranslatorInterface $translator)
{
... lines 22 - 23
$repLogAppProps = [
'itemOptions' => [],
];
foreach (RepLog::getThingsYouCanLiftChoices() as $label => $id) {
$repLogAppProps['itemOptions'][] = [
'id' => $id,
'text' => $translator->trans($label),
];
}
... lines 33 - 37
}
... lines 39 - 66

Cool! This code builds the structure we need: it has an itemOptions key, we loop over each, and create the id and text keys. Now when we refresh, Yep! The dumped code looks exactly like our REP_LOG_APP_PROPS JavaScript structure! Heck, we can add withHeart => true... because I like the heart.

... lines 1 - 19
public function indexAction(Request $request, RepLogRepository $replogRepo, UserRepository $userRepo, TranslatorInterface $translator)
{
... lines 22 - 23
$repLogAppProps = [
'withHeart' => true,
... line 26
];
... lines 28 - 38
}
... lines 40 - 67

Remove the die and pass this into twig as a new repLogAppProps variable.

... lines 1 - 34
return $this->render('lift/index.html.twig', array(
... line 36
'repLogAppProps' => $repLogAppProps,
));
... lines 39 - 67

Ready for the last piece? Delete the old JavaScript object and replace it with: {{ repLogAppProps|json_encode|raw }}.

... lines 1 - 63
{% block javascripts %}
... lines 65 - 66
<script>
window.REP_LOG_APP_PROPS = {{ repLogAppProps|json_encode|raw }};
</script>
... lines 70 - 71
{% endblock %}

That will print that array as JSON... which of course, is the same as JavaScript.

Ah, do you love it? It's now very easy to pass dynamic values or initial state into your app. Try it: refresh!

Removing the Old Code!

And... woh! Our app is now basically working! Yea, we're going to look at a few more things, but I think it's time to delete our old code and put this app into the right spot. In other words, it's time to celebrate!

Start by deleting this entire old Components directory: all of that code was used by the old app. Delete the old entry file - rep_log.js and inside of the template, we can remove a ton of old markup. The new lift-stuff-app div now lives right next to the leaderboard.

... lines 1 - 2
{% block body %}
<div class="row">
<div id="lift-stuff-app"></div>
<div class="col-md-5">
<div class="leaderboard">
... lines 9 - 16
</div>
</div>
</div>
{% endblock %}
... lines 21 - 36

Oh, and delete _form.html.twig too - more old markup. At the bottom, remove the original script tag.

And in webpack.config.js, delete the old entry. Wow! Webpack is angry because of the missing entry file. Stop and restart it:

yarn run encore dev-server

It builds! Go back and refresh! It's alive! And it works! Except... it's jumpy when it loads. The leaderboard starts on the left, then moves over once our app renders.

This is caused by a mistake I made. Look inside RepLogs. This is our main presentation component: it gives us all the markup. And, it has a col-md-7 class on it. Now, it's not wrong to put grid classes like this inside React. But, this top-level grid class is a bit weird: if we tried to use this component in a different place in our site, it would always have that col-md-7. It makes more sense sense to remove that class and, instead, in index.html.twig, add the class there. Now, our React app will just fit inside this.

... lines 1 - 2
{% block body %}
<div class="row">
<div id="lift-stuff-app" class="col-md-7"></div>
... lines 6 - 18
</div>
{% endblock %}
... lines 21 - 36

And when you reload the page, yes: no annoying jumping!

Next: we know that React can be used to create re-usable components... but we haven't really done this yet. Time to change that!

Leave a comment!

  • 2018-08-30 bartek1234321

    Hey Victor Bocharsky

    Thanks for reply!
    images are loaded through require and translations are provided as ready to use props but I'll try to use this bundle!
    Thank you very much for your advice!
    Have a nice day ;)

  • 2018-08-30 Victor Bocharsky

    Hey bartek1234321 ,

    Images are just HTML tags, so you need to pass a proper path to the image, to generate a proper path you need to use asset() Twig function. Then, just receive it and put into src attribute of img tag.

    Translations - good question. You can take a look at https://github.com/willdura... - it will give you a Translator object in JS with similar to Symfony Translator Component's API. Use it to translate messages on site. Or, as an alternative solution, if you don't want to use this bundle, you will need to pass already translated text, for it use trans() or transchoice() Twig functions.

    Cheers!

  • 2018-08-29 bartek1234321

    How to handle displaying images/translations? Generate it in twig and pass as props?

  • 2018-08-15 Diego Aguiar

    Hey Narong Pattanayanon

    You have to declare the window.REP_LOG_APP_PROPS object in your template

    Cheers!

  • 2018-08-15 Narong Pattanayanon

    window.REP_LOG_APP_PROPS is undefined!
    itemOptions: Array(0)