Anyone starting out as an aspiring developer, looking to learn skills in the frontend department will face the same, tough decision. Which framework or library should they use?
There are plenty of articles comparing React, Angular and Vue, e.g. here. I will not go into too depth as to why I’ve chosen to develop my frontend with React, however, I’ve had it recommended multiple times by people with lots of frontend experience. It’s also a lightweight library (and not a fully-fledged library), so it should leave me plenty of freedom. Finally, it seems to be the fastest growing frontend “language” which is searched for on the job market, which is definitely not insignificant.
In this article, I will describe how to create a React App, using the create-react-app template, set up some first components for my money management frontend where I will pass props, and then do some first styling of the new components.
Getting started
There are no dependencies required, aside from having npm installed. If you do not already have npm installed, you can use this tutorial.
I already mentioned everything would start with a template. Fortunately, the Facebook team behind the React development has created a starter called create-react-app, which will set up the skeleton for a new React application.
In order to run the starter, we first need the npx package runner tool. It comes with npm 5.2+
, but it can also be manually installed
by running npm install -g npx
.
Once you get a valid response to the command npm -v
(or npx -v
for that matter), we can run the starter using npx create-react-app money-management-frontend --template typescript
.
You may need to confirm the installation of the template starter if this is the first time you’ve used it. (In the picture, I missed the --template typescript
, so I’ve repeated the process.)
Once the starter has run (takes about 1 minute), you can then already start the application locally, using
cd money-management-frontend
npm start
That was easy enough - and we now have an application to develop!
Creating first components
As the application suggests, the first place to start changing the application is in the App.tsx
file.
Before we start doing this though, let’s first go over what we intend to do, and how we can incorporate the end goal of the web application here:
- Create some components
- Understand how to pass props
- See how to organize the folders and files
- Get started with styling
Very well, these are the general goals. In order to complete all of these, without routing for now, it seems like an overview of all personal accounts seems like a good place to start.
Creating a hard-coded account
Bearing in mind that the accounts will not be the only type of assets the application should track, the account-related components
will go into the src/assets/accounts
folder. In here, the components, functions and tests related to accounts with all go.
As for the coding style, I generally prefer the more modern, less verbose way of writing React. This means I will mostly use functions, and not React components. Similarly, I will mostly use hooks, and not Redux or lifecycle events.
Now, before the account itself can be represented, it needs to be defined. It will require an id, of course, a currency, a name that’s
easy to remember, etc.
So, overall, a first definition of the account shall be placed in the src/models/Account.ts
, and looks as follows:
export interface Account {
id?: number,
name: string,
description?: string,
currency: string,
amount: number
}
The first account component shall now be in src/assets/accounts/AccountItem.tsx
to avoid a clash with the interface it represents.
import {Account} from "../../models/Account";
export const AccountItem = () => {
const account: Account = { //hardcoded for now
amount: 320.8,
currency: "CHF",
name: "myNiceAccount"
};
return (
<tr>
<th scope="col" >
{account.name}
</th>
<th scope="col">
{account.currency} {account.amount}
</th>
<th scope="col">
{account.description}
</th>
</tr>
)
};
Well, this looks very promising, but we need to check out what it would actually look like. In order to do this, we remove the generated
code in the App.tsx
, and simply replace it with this new component. So now, the App.tsx
looks as follows:
function App() {
return (
<div className="App">
<AccountItem/>
</div>
);
}
Since the same App is still loaded in the index.html
, no further changes are required. The image below shows what that looks like.
Well, pretty is not the word I’d use, but nonetheless, everything is where it’s supposed to be.
Passing props
This is already a good start. However, it’s not very versatile. The account is defined right there in the component, and that’s not quite useful. I’m sure that someone out there has an account with precisely that much Swiss Francs in it, but would they want it to be called myNiceAccount? That’s not very likely.
Of course, in reality, a frontend application in a browser would load the correct values from a server. For now, we’ll keep
hardcoding the assumed values. However, we can go ahead and create a parent component for the account itself. Let’s call
it AccountList
(the next chapter will handle the list part, now we’re focusing on how to pass data to child components).
Accepting Props
Props, short for Properties, are the way of passing data from parent components to children. Children accept props in the signature of the component. Since we are using typescript, the correct structure of the props is also expected.
I like to define the props right next to the component definition, using the type
keyword. If we take our AccountItem
from
earlier, this would now look as follows:
type Props = {
account: Account
}
export const AccountItem = ({account}: Props) => {
return (
... // the rest of the code remains exactly the same
)
};
As you can see, the hardcoded account has been removed, and instead, the component expects to receive the account.
Passing Props
Well, the AccountItem
is expecting the props, so the AccountList
should pass it to its child. This is done in the tsx
itself, with the <Component propName={propValue} />
structure. As for the AccountList
, it can look like this:
import React from "react";
import {Account} from "../../models/Account";
import {AccountItem} from "./AccountItem";
function AccountTableList() {
const accounts: Account = {
amount: 320.8,
currency: "CHF",
name: "myNiceAccount",
description: "Wow, this is advancing!"
};
return (
<table>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Value</th>
<th scope="col">Description</th>
<th scope="col"/>
</tr>
</thead>
<tbody>
<AccountItem account={accounts}
/>
</tbody>
</table>
)
}
export default AccountTableList;
Of course, since there is now a layer in between the App
and the AccountItem
, this needs to be reflected in the App.tsx
.
function App() {
return (
<div className="App">
<AccountList/>
</div>
);
}
You may have noticed that I have also added a description in the account - this was done so that it would look nicer in the table.
The result has not really changed, except for the table that is now coming from the AccountList
.
The advantage for this is that now, the AccountItem
does not handle any state itself anymore. This makes it a so-called
pure component. What that essentially means is that, every time the same inputs are passed, the exact same output is
generated. This is especially useful for testing, as it is not dependent on anything outside it.
Handling lists
Aside from lacking nice styling, the AccountItem
looks pretty good now. It is receiving its data from its parent, so it
can already handle variable accounts. Different input would be handled automatically.
What would we need to do if we’d want multiple accounts though? We cannot create a new component for each account we’d have.
Instead, we are now going to focus on the handling of lists. Currently, aptly named, the AccountList
should have all the account
information and pass this to each child, respectively.
How are lists generated though. This is another time when we add some typescript inside the tsx. The structure is done with iterating over the list using arrow functions.
{ list.map((item) => <div key={item.id} />)}
This is only the general structure, of course. You may notice the key
prop - this is a React best practice. This is added
in every list to let React know which item potentially needs to be re-rendered in its virtual dom.
If we now apply this to the AccountList
, and add a new account as well, it will look like this:
function AccountTableList() {
const accounts: Array<Account> = [{
id: 1,
amount: 320.8,
currency: "CHF",
name: "myNiceAccount",
description: "Wow, this is advancing!"
}, {
id: 2,
amount: 94.6,
currency: "EUR",
name: "eurozz",
}];
return (
<table className="table table-striped">
<thead>
// hasn't changed
</thead>
<tbody>
{accounts.map(acc => <AccountItem key={acc.id} account={acc}/>)}
</tbody>
</table>
)
}
As you can see, the second account now also appears.
Styling
I can already imagine what many readers are thinking - “All of this is great, but it still looks horrible. The reason we’re getting into Frontend is we want to make stuff look good”.
Things can be styled in many ways. There are multitudes of libraries out there for this, simple css can be included in the tsx, it can be done with styled components, etc. I will go over some options here.
Adding inline css in tsx
Since React doesn’t use html, but tsx, some html additions may look a little different. For example, the usual kebab-case
of css is replaced with camelCase. Classes are not added with class
, but instead with className
.
If you want to include css directly in the tsx, you need to add a JSON object (hence the double curly braces)
of styles inside the component. For example, you could adapt the first column of the AccountItem
like follows,
with the result below.
<th scope="col" style={{backgroundColor: "green", textAlign: "right"}}>
{account.name}
</th>
CSS modules
So, you can of course use global cascading style sheets. CSS in separate files is usually a better idea than simply having it inline, as it can be made reusable.
However, in big projects, this can become quite cumbersome, as you need to pay attention to not reuse the same classes and ids. React has a solution for this - CSS modules. If you have not used create-react-app, you may need additional configuration to enable CSS modules - however, in my template, they are automatically enabled.
The idea behind the modules is that you can define some CSS, import it in the required file, and in the end, the tsx will be compiled in such a way, that it is only valid for that very component. (Note: the css doesn’t actually have to be in a separate file, it could just be in the same if having the separation is not that important to you.)
All that is required is some CSS definitions in a file, which you can then import, for example with import styles from './AccountItem.module.css';
.
Now, in order for this to work, the CSS file itself needs to have the extension module.css
, so the name of the file below is
AccountItem.module.css
.
.first {
font-size: 25px;
}
.second{
background: aqua;
font-weight: lighter;
}
Next, the styles can be used as classes. For example, some small class additions in the AccountItem
are needed, and voilà -
the styles are changed. The cool thing here is that you can really choose in which files you wish to add these classes,
and you don’t really need to pay as much attention to the class names as you otherwise would have to!
export const AccountItem = ({account}: Props) => {
return (
<tr className="d-flex">
<th scope="col" className={styles.first}>
{account.name}
</th>
<th scope="col" className={styles.second}>
{account.currency} {account.amount}
</th>
<th scope="col">
{account.description}
</th>
</tr>
)
};
If you inspect the generated code, you will see some strange looking classnames. This is simply the way how React defines the generated classes to ensure that they will not conflict with other classes that may be defined.
Libraries
Then of course, there are libraries which come with default styling. Some commonly used ones are react-bootstrap or Material UI. I myself will be mostly using Material UI, as I like the simplistic look and feel they give. Also, this is the styling generally used by Google’s applications, so it will immediately be very familiar to your users.
If there are certain changes that I’d like to add to the components, I may use a mix of inline styles and CSS modules also.
Mostly CSS modules, really, as inline styles are easier to overlook, and really just not reusable. Simply
choose whatever you are most comfortable with.
In order to add Material UI to the project, you need to run npm install @material-ui/core
, and now you have all the
components and styling from this library at your disposal. There are some, more fancy components, that may require additional
(or even experimental) libraries, but they are added essentially the same way.
Now, since we are currently still displaying the accounts in a table, I will display it as such. The nice thing about using libraries is that they are often well documented (not all obviously, but Google knows what they’re doing). As an example, you can find the API of the table component here.
Adapting the components to the chosen MUI styles will immediately give such a more satisfying result.
import React from "react";
import {Account} from "../../models/Account";
import {AccountItem} from "./AccountItem";
import classes from "./AccountList.module.css";
import TableContainer from "@material-ui/core/TableContainer";
import Table from "@material-ui/core/Table";
import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow";
import TableCell from "@material-ui/core/TableCell";
import TableBody from "@material-ui/core/TableBody";
import Paper from "@material-ui/core/Paper";
function AccountTableList() {
const accounts: Array<Account> = [{
id: 1,
amount: 320.8,
currency: "CHF",
name: "myNiceAccount",
description: "Wow, this is advancing!"
}, {
id: 2,
amount: 94.6,
currency: "EUR",
name: "eurozz",
}];
return (
<div>
<Paper elevation={6} className={classes.root}>
<TableContainer className={classes.container}>
<Table stickyHeader aria-label="sticky table">
<TableHead>
<TableRow>
<TableCell align={'center'}>
Name
</TableCell>
<TableCell align={'center'}>
Value
</TableCell>
<TableCell align={'center'}>
Description
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{accounts.map((account) => {
return (
<AccountItem key={account.id} account={account}/>
);
})}
</TableBody>
</Table>
</TableContainer>
</Paper>
</div>
)
}
export default AccountTableList;
import {Account} from "../../models/Account";
import TableCell from '@material-ui/core/TableCell';
import TableRow from '@material-ui/core/TableRow';
import React from "react";
type Props = {
account: Account
}
export const AccountItem = ({account}: Props) => {
return (
<TableRow hover role="checkbox" tabIndex={-1}>
<TableCell align={'center'}>
{account.name}
</TableCell>
<TableCell align={'center'}>
{account.currency} {account.amount}
</TableCell>
<TableCell align={'center'}>
{account.description}
</TableCell>
</TableRow>
);
};
.root {
width: 70%;
margin: 10px auto auto;
}
.container {
max-height: 70%;
}
As you can see, I’ve already started mixing and matching the libraries with CSS modules. Now, with MUI, there is another
way of doing the same, but slightly more convenient, which would be the useStyles
method. However, in this article, I wanted
to show that you really are free to use whichever method you’d like.
Overall, the result may still look quite foreign to you, since there is only the table. There is no Navigation Bar, nothing on the sides, no footer. Then again, we have just now already created a dynamic list of items (or at least one that can handle dynamic information), passing the data down to its children, and the data is nicely represented. Not bad for a short day’s work!