Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
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.

Start your All-Access Pass
Buy just this tutorial for $12.00

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!

13
Login or Register to join the conversation
Mouad E. Avatar
Mouad E. Avatar Mouad E. | posted 4 years ago

Hello, please fix RepLogController ====> newRepLogAction it not returning the correct response content:

//$response = $this->createApiResponse($apiModel);
this line must be uncommented

Reply
Mouad E. Avatar

sorry it's my mistake

Reply

Hey Mouerr,

No problem! Sometimes we may miss something, so thank you for keeping your eyes on it ;)

Cheers!

1 Reply

Hey Mouerr,

Thank you for reporting this! Though I downloaded the course code and in both start/ and finish/ directories that line is uncommented. Could you give me more context? Where exactly you see that line commented out?

Cheers!

Reply

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

Reply
Victor Avatar Victor | SFCASTS | bartek | posted 4 years ago | edited

Hey bartek ,

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!

Reply

Hey victor

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

16 Reply

Hi again! ;) Do you know how to set up BazingaJsTranslationBundle to work with webpack encore correctly? I don't want to use it globally but through import statement

3 Reply
Victor Avatar Victor | SFCASTS | bartek | posted 4 years ago | edited

Hey bartek ,

IIRC, you can install it via NPM (or Yarn) and import it in your JS files: https://github.com/willdura...

Did you try this?

Cheers!

Reply

Sorry for late reply victor ;)

yes, know it works fine but installed it by composer and yarn.

I use webpack and import Translator works but to deliver translations to Translator i used dump method but not loaded by xhr like it was written in doc but I wanted to get the same way as in jsrouting-bundle so I did it like that

const locale = document.querySelector('.js-request').dataset.locale;
import Translator from 'bazinga-translator';
const myRetrievedJSONString = require(`../../../public/js/translations/messages/${locale}.json`);
Translator.fromJSON(myRetrievedJSONString);

if you think that this solution is wrong please tell me ;)
thnkas for your time!

Reply
Victor Avatar Victor | SFCASTS | bartek | posted 4 years ago | edited

Hey bartek ,

Ah, ok, sounds reasonable. Though if I understand you right - I'd recommend you to use "dump" command in production to dump all the translations you needed to a static file, see: https://github.com/willdura... . And then just include/use that file directly in production instead of "{{ url('bazinga_jstranslation_js', { '_format': 'json' }) }}" or custom "Translator.fromJSON(myRetrievedJSONString);". And every time you add/update translations - you would need to re-dump them again. But in development, probably not a big deal to use the recommended "{{ url('bazinga_jstranslation_js', { '_format': 'json' }) }}".

Cheers!

Reply
Default user avatar
Default user avatar Narong Pattanayanon | posted 5 years ago

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

Reply

Hey Narong Pattanayanon

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

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

While the concepts of this course are still largely applicable, it's built using an older version of Symfony (4) and React (16).

What PHP libraries does this tutorial use?

// 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
    }
}

What JavaScript libraries does this tutorial use?

// 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
    }
}
userVoice