Among the heavy hitters in the frontend web application frameworks, React
, by the lovely engineers at Facebook, is easily one of the most popular. According to the React docs, React is:
A javascript library for building User Interfaces.
React gives us a powerful API for build component-based, declarative, UI elements. We can then compose these components together to make feature-rich and powerful web applications. The key here is the component-based architecture. Components are the building blocks of React applications and they should encapsulate a single piece of UI functionality as well as maintain their own state
. Input data can be passed into a component via the component props
. The component can interact with these props
and display them through the component render
to be displayed to the user in the UI.
Table of contents
TypeScript
To make our components resilient, and better understand the data props
being passed into them, we can add TypeScript
support to our React applications and type our props
. By adding this support, our app can use the power of TypeScript
to provide intellisense, linting, and compile-time errors; these work together to help engineers build and compose components. Doing this, if we try to pass in incorrect data to a component, our app will fail to compile vs. failing at runtime. This means these failures should not reach the end-user.
But what is TypeScript
? Created by Microsoft, according to the docs:
TypeScript is a typed superset of JavaScript that compiles to plain JavaScript
TypeScript
works on any browser, on any OS, and can be used to build scalable, powerful NodeJs
APIs as well.
Let’s Build an App
Time to put these tools to use. We will build a React
application with TypeScript
support to display and interact with some data and see the value of building our React
components using `TypeScript.
Create the App
First, lets bootstrap our app using the create-react-app
script:
npx create-react-app my-dive-log --typescript
# or
yarn create react-app my-dive-log --typescript
Note: per the create-react-app docs, they recommend uninstalling the create-react-app
if you have it installed globally and using one of the methods listed above to guarantee you are using the most up-to-date version.
Free eBook
Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.
- Observables and Async Pipe
- Identity Checking and Performance
- Web Components <ng-template> syntax
- <ng-container> and Observable Composition
- Advanced Rendering Patterns
- Setters and Getters for Styles and Class Bindings
Once finished, we have a directory: my-dive-log
with a few files generated by the script to get us started:
|- node_modules
|- public
| |- favicon.ico
| |- index.html # <- root app page where the compiled JavaScript and css are injected to run the app
| |- manifest.json # <- used for PWA
|- src # <- app code root directory
| |- App.tsx # <- Describes our Application JSX. Top-Level component
| |- App.css # <- App component styles source
| |- App.test.tsx # <- App.tsx unit test file
| |- index.tsx # <- application root file where the App is registered with the ReactDOM into the root element in the index.html
| |- index.css # <- root app level styles
| |- serviceWorker.ts # <- PWA service worker code
|- .gitignore
|- package.json
|- tsconfig.json # <- TypeScript configuration file. Specifies the root files and compiler options
This provides us with the required packages and files to establish our React app as a TypeScript project so it can be compiled and ran. If you look at the scripts in the package.json
file, you will see a few scripts like start
. This uses the react-scripts start
while will compile and run our application. Open a terminal in this directory and let us run it:
yarn start
# or, with npm
npm start
If successful, a browser window should open with our app!
TypeScript Linting
The addition of TypeScript
help to enforce not only type-safety but allows us to introduce rules to make our code more readable, maintainable, and functionality-error free. These rules are established through a static analysis tool calling tslint
. It allows us to adopt industry-standard linting and gives us the ability to override rules as you see fit (want longer print lines? single-quotes vs double quotes? etc). Let’s add tslint
and a couple tslint
rules libraries to our app to enforce quality coding practices:
yarn add --dev tslint tslint-config-prettier tslint-react
# with npm
npm install --save-dev tslint tslint-config-prettier tslint-react
tslint
is the base static analysis tools libtslint-config-prettier
is a tslint plugin library to have tslint behave well with prettier; if you don’t use prettier (which I highly recommend), then this library is not necessarytslint-react
is a tslint plugin library that has rules around utilizing TypeScript with React
To add support, create a file in the root directory called tslint.json
; this is where we establish and can override our tslint
rules:
{
extends: ['tslint-react', 'tslint-config-prettier'],
linterOptions: {
exclude: [
'config/**/*.js',
'node_modules/**/*.ts',
'coverage/lcov-report/*.js',
],
},
rules: {
'jsx-no-lambda': false,
},
defaultSeverity: 'warning',
}
Pretty basic right now, we extend the tslint-react
& tslint-config-prettier
plugin libraries to utilize the rules they have established with 1 override in the rules
section: "jsx-no-lambda": false
; this is just a personal preference to turn off the jsx-no-lambda
rule.
A look at our App React Functional Component
As mentioned, one of the files generated for us is ./src/App.tsx
. Open this file and take a look:
import React from 'react';
import logo from './logo.svg';
import './App.css';
const App: React.FC = () => {
return (
<main className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</main>
);
};
export default App;
The key here is that we have a const
variable named App
which is of type React.FC
or Functional Component
. React promotes 2 types of components: Class Components and Functional Components. The idea of a Functional Component is that it is a pure function; pure meaning - its return value is only determined by the input values (it does not use-values outside of those passed to it as input), its return values are always the same given the same input. Pure functions are the way to be as they are much easier to encapsulate, test, maintain, and read. By design, functional components are stateless
; meaning they do not have access to state
or have a lifcecyle; this was until the addition of hooks in React 16.8. Now with hooks, we can use React Functional Components that interact with our state as well. Hooks are amazing and I will be writing another post about hooks in this article series; stay tuned.
React works on the component concept that allows us to build components and then compose them together. Our App
component defined here will be our top-level component where we will compose our other components as children. I like to think of this as our App shell where we can define components that will be shared across our app; things such as our app header, with navigation, authenticated user info, etc; an app footer if needed, our top-level route definitions if needed; etc.
We will build a couple of components in this article and add them to this App
component.
App Header
To start, let’s create a header component that will sit at the top of our app; show the app title, a short description, and a couple of navigation links.
Create a folder inside of ./src
named containers
; this is where we will place smart “containing” components.
mkdir ./src/containers
And inside of this directory, create a file named AppHeader.tsx
; note the .tsx
extension, this declares our file as a JSX
file with TypeScript
. Using this extension allows us to have type-safety with our components and reap the advantages of TypeScript.
touch ./src/containers/AppHeader.tsx
# create the css file as well
touch ./src/containers/AppHeader.css
Open the created AppHeader.tsx
file and let’s create a React Functional Component that takes in props for use in our header.
import React from 'react';
import './AppHeader.css';
import AppHeaderNavLink, { NavLink } from './AppHeaderNavLink';
// define our AppHeader properties that will be passed into the component
export type AppHeaderProps = {
title: string;
description: string;
links: NavLink[];
};
// create React Functional Component variable that will render our code for our header.
// React.FC takes a type of the props that can be passed into the component
const AppHeader: React.FC<AppHeaderProps> = React.memo(
({ title, description, links }) => {
return (
<header className="app-header">
<section className="app-title">
<h1>{title}</h1>
<small>{description}</small>
</section>
<span className="fill-space" />
<section className="app-links">
{links &&
links.map((link: NavLink) => (
<AppHeaderNavLink
label={link.label}
route={link.route}
key={link.label}
/>
))}
</section>
</header>
);
}
);
export default AppHeader;
First, we define a type
; AppHeaderProps
that represents our props. This type will have the app title, description, and an array of links of type NavLink
(described next). This type will be declared as the type on our AppHeader
Functional Component. This is what the component will expect as props. If a value for a prop is not provided, we will see the little red-squiggly line under our component declaration telling us that it is missing the given type. Same if we give a prop an incorrect value type. This is the magic of adding TypeScript to our React components. It helps us better understand what our components expect and will fail at compile time, instead of runtime. This also helps new engineers to a codebase pickup and work in the code quicker as the tooling helps them learn and understand the component and how to compose them together to build out the app.
Navigation Links Component with React.memo
In the AppHeader
component you can see we bring in another component as well: AppHeaderNavLink
, we will go through this component now. It is a simple component that takes and displays a NavLink
so that we can add routing to our component; it is a good show of the composability of React and the use of array.map
to iterate over an array and compose components. Note This will not route anywhere just yet, there will be an article in this series on react routing as well.
We will create this component, along with its styling in a components
directory. This directory will contain low-level view components (sometimes called dumb components). These components should not be responsible for anything more than receiving and displaying its received props. Any business logic should be done in a container component. Create the components
directory and this AppHeaderNavLink.tsx
file in the directory:
# create directory
mkdir ./src/components
# create AppHeaderNavLink files
touch ./src/components/AppHeaderNavLink.tsx
touch ./src/components/AppHeaderNavLink.css
And open the created AppHeaderNavLink.tsx
:
import React from 'react';
import './AppHeaderNavLink.css';
// define a Navigation Link type for our links
export type NavLink = {
label: string;
route: string;
};
const AppHeaderNavLink: React.FC<NavLink> = React.memo(({ label }) => (
<span className="nav-link">{label}</span>
));
export default AppHeaderNavLink;
Just like the AppHeader
component above, we define a const
variable of type React.FC
, or functional component, that takes NavLink
for its prop types. We did introduce another React concept here: React.memo
. This function was introduced with React 16.6. memo
is short for memoization
. This is a concept to speed up the rendering of a component by memoizing the rendered result. What this does is before the next render of the component, if the props passed into the component are the same from the previous render, the memoized result will be reused; saving time. As with anything, there are tradeoffs and React.memo
should not be used in all situations (still no silver bullets). Check out this article on good use-cases on when to use React memoization here.
Adding the AppHeader
to App
With our AppHeader
component built, we need to add it to our App.tsx
file to display the header:
First, we need to import the AppHeader
, and we will also import the AppHeaderProps
to make an instance of these props to pass to the component:
import AppHeader, { AppHeaderProps } from './containers/AppHeader';
// build an instance of our AppHeaderProps to pass to the AppHeader component
const headerProps: AppHeaderProps = {
title: 'My Dive Log',
description: 'Log, Track, Review your dive logs and relive the experience',
links: [
{
label: 'Logs',
route: '/logs/list',
},
{
label: 'New Entry',
route: '/logs/create',
},
],
};
We will now add this component to the App
render as a child component:
const App: React.FC = () => (
<main className="App">
<AppHeader
title={headerProps.title}
description={headerProps.description}
links={headerProps.links}
/>
</main>
);
I replaced all the content inside of the top-level main
element and added the header here as the first child in the tree. Just like adding the AppHeaderNavLink
component to our AppHeader
, we compose the AppHeader
into App
. This component-based design is the crux of the React framework. Build encapsulated, readable, single-use components to build UI functionality and then compose these components together to build an app.
App Body
With our header in place, let’s give some content to the body of the app to finish out this first iteration. Create another container component in our containers
directory called AppBody
(I know, super great name).
# create AppBody tsx file
touch ./src/containers/AppBody.tsx
# and create AppBody css styles
touch ./src/containers/AppBody.css
Let’s add some basic props to display a header, a quote, and some children
in this area.
import React from 'react';
const AppBody: React.FC<{ header: string; quote?: string }> = ({
header,
quote,
children,
}) => (
<section className="app-body">
<section className="body-content">
<h2>{header}</h2>
{quote && <blockquote>{quote}</blockquote>}
</section>
{children && { children }}
</section>
);
export default AppBody;
Unlike in our AppHeader
, I did not create a type
for the props and built it out inline. This works just fine if we have a limited number of props for a component that we will not reuse in other areas of the app. Our AppBody
also exposes another react concept you may be familiar with; children
. children
is Reacts nomenclature of child elements that can be passed into the component and displayed. These elements can be other html elements like a div
or span
, etc; or it can be components that we have created. This is a way for a component to declare that whoever utilizes this component can pass in any children
as they wish and they will be rendered into the DOM where this component places them. This is a very powerful method of composing components in our app. You also see a way to test if a prop has a value before adding an element for the prop into the DOM:
{
quote && <blockquote>{quote}</blockquote>;
}
This is how we can add/remove elements to/from the DOM. This says if the prop quote
is not null or undefined, then add the blockquote
element to the DOM to display the quote.
Open App
again and lets import and add the AppBody
to our app and pass some children in. First, we need to import the AppBody
:
import AppBody from './containers/AppBody';
And now we will compose it into our component. I generated some sweet bacon ipsum text for our quote to pass into the body:
const bodyQuote = `
Spicy jalapeno bacon ipsum dolor amet ball tip turducken brisket veniam beef ribs ipsum, ex pig doner strip steak t-bone.
Bacon swine shankle, pastrami tail chuck strip steak kevin.
T-bone mollit kevin chicken id sirloin tenderloin irure pork chop ball tip lorem qui.
Tenderloin et tri-tip, porchetta cillum in occaecat. Cow sint magna pork loin, officia laboris in boudin doner.
Frankfurter burgdoggen cupim, pariatur consequat salami tempor.
Bresaola jerky laboris alcatra shoulder filet mignon exercitation proident non. Leberkas hamburger aute labore meatball.
Shank labore reprehenderit culpa. Buffalo eu shankle chuck sed cillum ut burgdoggen turducken bresaola pariatur landjaeger.
Consectetur excepteur burgdoggen filet mignon enim, boudin ad pork chop. Turducken ut sint, cow pork chop dolore chicken reprehenderit jowl.
Ad pariatur pig fatback.
`;
const App: React.FC = () => (
<main className="App">
<AppHeader
title={headerProps.title}
description={headerProps.description}
links={headerProps.links}
/>
<AppBody header="My Dive Log Dashboard" quote={bodyQuote}>
<p>
I am a child element to the App Body and will be rendered in the App
Body
</p>
</AppBody>
</main>
);
There we go. Now we have some body content in our app that had a header, some tasty meat quotes and as I mentioned, we passed in a child element p
basic HTML element to the AppBody
as well and it is displayed where we placed our children in the AppBody
. We can add as many, or none, children in here as we want and they will all be added to the DOM.
Conclusion
With our App
built out, we can open a terminal in the root directory and start the app and we should see the AppHeader
and below it our AppBody
:
yarn start
# or with NPM
npm stat
React is a great framework for building very powerful apps working on this same concept of component-based design. This scales very well as we can add more and more components to use as building blocks to build out our app. This first article goes over this component concept as well as how to use TypeScript
in conjunction with React
to create type-safe, readable, maintainable components. The next article in this series will go over the concept of Hooks
as we will start to make our components a little smarter and able to manage their state. Stay tuned for more to come and I hope you enjoyed this article.