This course is archived!
While the concepts of this course are still largely applicable, it's built using an older version of Symfony (4) and React (16).
Passing Server Data to React Props
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeWe 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!
Hello, please fix RepLogController ====> newRepLogAction it not returning the correct response content:
//$response = $this->createApiResponse($apiModel);
this line must be uncommented