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

Collection & Rendering a Table

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

The RepLogApp component eventually needs to render all the elements we see on the original app: with a table and a form. No problem! Go find the template for this page: templates/lift/index.html.twig. Hey! There's our table! And the form lives in this included template. Copy the entire old markup. Then, go back to RepLogApp and replace our code with this. But, I don't want to worry about the form yet... so remove that. Oh, and I kinda liked the withHeart feature, so let's make sure we're still printing {heart}.

... lines 1 - 2
export default class RepLogApp extends Component {
render() {
... lines 5 - 9
return (
<div className="col-md-7 js-rep-log-table">
<h2>Lift Stuff! {heart}</h2>
<table className="table table-striped">
<thead>
<tr>
<th>What</th>
<th>How many times?</th>
<th>Weight</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td>&nbsp;</td>
<th>Total</th>
<th className="js-total-weight"></th>
<td>&nbsp;</td>
</tr>
</tfoot>
</table>
</div>
);
}
}

class -> className

Sweet! Once again, PhpStorm just did an amazing thing - automagically - when we pasted in the code. Check out the className="table table-striped". Hmm, look at the original code in the template: it had class=""... because that's what it should be in HTML! Well... in React, you actually can't use class. You can see it's highlighted:

Unknown property class found, use className instead

React can't use class because class is a keyword inside of JavaScript. And for that reason, in JSX, you need to use className instead. But ultimately, this will render as a normal class attribute on the page.

And, don't worry, there aren't tons of weird attributes like this in React: this is basically the only one you're likely to use.

The point is: PhpStorm is smart enough to convert our pasted class props to className automatically. Notice that I said props: while we think of these as HTML attributes, they're technically props, which React ultimately renders as attributes.

Finish our Static "Mock Up"

Let's clean a few things up! We don't need this js-rep-log-table class: it was used by the old JavaScript. And below, this is the column that prints the total weight. Remove the class that was used by the old JavaScript and, for now, just put a TODO inside.

And finally, just to see how it looks with data, let's hack in one fake row full of invented stuff. Use ... for the last column: someday, we'll add a delete link here.

... lines 1 - 9
return (
<div className="col-md-7">
... lines 12 - 13
<table className="table table-striped">
... lines 15 - 22
<tbody>
<tr>
<td>Big Fat Cat</td>
<td>10</td>
<td>180</td>
<td>...</td>
</tr>
</tbody>
<tfoot>
<tr>
... lines 33 - 34
<th>TODO</th>
... line 36
</tr>
</tfoot>
</table>
</div>
... line 41
);
... lines 43 - 45

Cool! Building a static version of your app first is a great way to start. And JSX makes that really easy.

Let's go check it out: find your browser and refresh! Hey hey! This is starting to look real!

Use Static Data First

In our old app, on page load, we make an AJAX request to load the "rep logs"- that's what we call our data - and use that to render the table.

Eventually, we'll do the same thing in React. But before you work with dynamic data, you should first make your app render using static data. Check this out, inside render(), create a new constant called repLogs and then set that to some fake data that matches the format of your API. We now have 3 fake rep logs with id, itemLabel and totalWeight.

... lines 1 - 3
render() {
... lines 5 - 9
const repLogs = [
{ id: 1, reps: 25, itemLabel: 'My Laptop', totalWeightLifted: 112.5 },
{ id: 2, reps: 10, itemLabel: 'Big Fat Cat', totalWeightLifted: 180 },
{ id: 8, reps: 4, itemLabel: 'Big Fat Cat', totalWeightLifted: 72 }
];
... lines 15 - 54
}
... lines 56 - 57

Rendering a Collection

Below, inside the tbody, we basically want to convert each "rep log" into a tr React element with the data printed inside of it. To do that, we're going to use a really common pattern in React... which might feel a bit weird at first.

Above render(), create a new constant called repLogElement set to repLogs.map(). Pass this a callback function with one argument: repLog. I'll use the arrow syntax for the callback. Inside, we're going to return a React element via JSX: add parenthesis so we can use multiple lines. Then, just build out the row: <tr>, then <td> with {repLog.itemLabel}.

If you're not familiar with the map function, that's ok: it's much less common in PHP. Basically, it loops over each element in repLogs, calls our function, and then, whatever our function returns, is added to the repLogElement array. So, ultimately, repLogElement will be an array of <tr> React element objects.

Add the next <td>. Let's see... ah, this column is "How Many". Fill in the second column with {repLog.reps}, then another <td> with {repLog.totalWeightLifted} and finally one more with ...: this will be the delete link... someday.

.

... lines 1 - 15
const repLogElements = repLogs.map((repLog) => {
return (
<tr key={repLog.id}>
<td>{repLog.itemLabel}</td>
<td>{repLog.reps}</td>
<td>{repLog.totalWeightLifted}</td>
<td>...</td>
</tr>
)
});
... lines 26 - 57

Great! Wait... but the tr has a little warning: something about a missing key prop. We'll talk about that in a minute. Until then, ignore that silly warning! What could go wrong?!

Now that we have an array of React element objects, this is pretty sweet: go down, delete the hardcoded row and - wait for it - just print repLogElements.

Yea, it looks a bit crazy: we're literally printing an array of React elements! But, try it - go back to your browser and refresh! It works! It prints each row!

But, we have a big warning from React. Let's fix that next, and introduce a new best practice to keep our code readable.

Leave a comment!

4
Login or Register to join the conversation
Dmitriy Avatar
Dmitriy Avatar Dmitriy | posted 4 years ago

Unfortunately, my PHPStorm does not automatically convert HTML to JSX. How can I set it up?

Reply

Hey Dmitriy

What you mean? Does your PHPStorm is not recognizing JSX syntax? If that's the case you have to enable it by going to Settings > Languages & Frameworks > Javascript - in there, you have to choose "React JSX", just that I'm not sure if you have to install a PHPstorm plugin first.

Cheers!

Reply
Dmitriy Avatar

No, I would like that when inserting HTML code in my document (in "render" function), it would automatically be converted to the JSX, like this video.

For this document i enable Settings > Languages & Frameworks > Javascript > React JSX, but it does not work.

Which plugin can do this?

Reply

Ah, I got you. I would think that that should be enough but probably you need to install "eslint" as well. Check this video to know how to install & configure it: https://symfonycasts.com/sc...

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