Skip to main content

Lesson 9 - React 2: More Hooks, Libraries, Styling, and Routing

Introduction

Welcome to the second React lesson of the course! In this reading, we'll begin with a review of hooks and the useState hook which we covered in our last reading. We'll then dive into the useEffect hook. Together, useState and useEffect are extremely powerful, and are probably the only 2 hooks you'll need in this course.

After looking at hooks, we'll delve into how styling works in React, covering a couple ways that you can use to ensure that your React apps look as beautiful as your websites did in the first part of the course.

Next, we'll dive into the notion of libraries in React and the powerful ecosystem that surrounds it. From there, we'll cover component libraries, a powerful way to write less code and create stunning UI's in minutes as compared to hours.

Finally, we'll touch upon routing and how to easily add multiple pages to your websites in React.

useEffect

Let's start with a quick review of hooks!

Hook review

useEffect is a hook. Hooks in React manage the state of your application. Essentially, hooks are functions that let you “hook into” React state and lifecycle features from functional components. React provides a few built-in Hooks like useState. You can also create your own Hooks to reuse stateful behavior between different components. In the React 1 Reading, you learnt about the useState hook, which we used to initialize a variable, along with an updater function for that variable. Let's do a quick review of useState before moving on.

useState Review

Let's recreate a counter example, just like we did in the React 1 Reading.

This example renders a counter. When you click the button, it increments the value of count.

import React, { useState } from 'react';

const Counter = () => {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
};

Here, useState is a Hook. We call it inside of a functional component in React to manage the state of our application. useState returns a pair: the current state value and a function that lets you update it. You can call this function from anywhere to update the current state value. In our example above, we initialized count to have a starting state value of 0 and setCount to a function which updates our count variable. The only argument to useState is the initial state. In the example above, it is 0 because our counter starts from zero.

What Do Square Brackets Mean?

You might have noticed the square brackets when we declare a state variable:

const [count, setCount] = useState(0);

The names on the left aren’t a part of the React API. You can name your own state variables:

const [fruit, setFruit] = useState('banana');

This JavaScript syntax is called “array destructuring”. It means that we’re making two new variables fruit and setFruit, where fruit is set to the first value returned by useState, and setFruit is the second. It is equivalent to this code:

var fruitStateVariable = useState('banana'); // Returns a pair
var fruit = fruitStateVariable[0]; // First item in a pair
var setFruit = fruitStateVariable[1]; // Second item in a pair

When we declare a state variable with useState, it returns a pair — an array with two items. The first item is the current value, and the second is a function that lets us update it. Using [0] and [1] to access them is a bit confusing because they have a specific meaning. This is why we use array destructuring instead.

Declaring Multiple State Variables

Declaring state variables as a pair of [something, setSomething] is also handy because it lets us give different names to different state variables if we want to use more than one:

const ExampleWithManyStates = () => {
// Declare multiple state variables!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
// ...
};

In the above component, we have age, fruit, and todos as local variables, and we can update them individually:

function handleOrangeClick() {
setFruit('orange');
}

You don’t have to use many state variables. State variables can hold objects and arrays just fine, so you can still group related data together.

Using useEffect

Importing useEffect

Similar to useState and any other hook we might want to use, we import useEffect like so.

import React, { useState, useEffect } from 'react';

If we just wanted to import useEffect and not useState, we would do the following:

import React, { useEffect } from 'react';

Counter Example

The useEffect hook lets you perform side effects in function components:

import React, { useState, useEffect } from 'react';

function Example() {
const [count, setCount] = useState(0);

useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}

This code snippet is based on on our previous counter example, but we added a new feature to it: we set the document title to a custom message including the number of clicks.

Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects.

Sometimes, we want to run some additional code after React has updated the DOM. Network requests, manual DOM mutations, and logging are some common examples of effects.

Details

What does useEffect do? By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we'll refer to it as our "effect"), and call it later after performing the DOM updates. In this effect, we set the document title, but we could also perform data fetching or call some other imperative API.

Why is useEffect called inside a component? Placing useEffect inside the component lets us access the count state variable (or any props) right from the effect. We don't need a special API to read it -- it's already in the function scope.

Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. (We will later talk about how to customize this). Instead of thinking in terms of "mounting" and "updating", you might find it easier to think that effects happen "after render". React guarantees the DOM has been updated by the time it runs the effects.

Now that we know more about effects, these lines should make sense:

function Example() {
const [count, setCount] = useState(0);

useEffect(() => {
document.title = `You clicked ${count} times`;
});
}

We declare the count state variable, and then we tell React we need to use an effect. We pass a function to the useEffect Hook. This function we pass is our effect. Inside our effect, we set the document title using the document.title browser API. We can read the latest count inside the effect because it's in the scope of our function. When React renders our component, it will remember the effect we used, and then run our effect after updating the DOM. This happens for every render, including the first one.

General Usage

Now that we've learnt the basics of hooks and useEffect, lets take another look at how to use useEffect in a more general setting. The general format of auseEffect hook is as follows.

useEffect(() => {
// put your code here
}, dependencies);

Essentially, useEffect is a function that takes in two arguements. The first arguement is a function with what you want your useEffect hook to do. This function takes in no arguements, hence the empty brackets at the beginning of our passed in arrow function. The second arguement, which is optional, is an array of dependencies. For example, let's say that inside of our useEffect hook, we want to update our score variable, but only if the referee variable has changed. It would look something like this:

useEffect(() => {
setScore(someNewScore);
}, [referee]);

Thus we only setScore to our new score value if the referee variable changes. If we wanted to set it so that we only updated the score based on either the referee variable or the inning variable changing, we could do something like this.

useEffect(() => {
setScore(someNewScore);
}, [referee, inning]);

Running useEffect on First render

If the useEffect hook seemed confusing to you, that's totally fine! Just make sure to pay attention here.

If you want to update something in your app, or have something happen right at page load, you can put it into the useEffect function, and pass in an empty array into it's list of dependencies. For example, say I wanted to print "Hello World" to the console right at page load. I would do the following.

useEffect(() => {
console.log('Hello World');
}, []);

Notice the empty array that was passed into the second arguement of our useEffect. This ensures that whatever's in our useEffect function only runs on page load. This will come up more often than not when you're building and testing any future projects.

Styling in React

There are many, many ways to style your components and JSX code in React. In this reading we'll talk about CSS-in-JS, CSS Modules, and Styled Components.

CSS-in-JS

CSS-in-JS is a term you'll hear often, but what exactly does it mean? CSS-in-JS usually means using an alternative method of styling than just plain CSS files, such as styled-components or css-modules. These solutions often require some change in the javascript that is being written, which is why we call these solutions "CSS in JS".

Why do we care about using CSS-in-JS? This is because all CSS in our react app is global. Say you have two components, Page1 and Page2. Even if you create page1.css and page2.css and only import page1.css into your Page1 component, all the classes you create in page1.css will apply to your Page2 component as well. Thus if you accidentally use the same class name across both components, or use a class name multiple times anywhere in your app, the CSS rules you've written in page1.css will apply everywhere, which in most cases is highly undesirable and can cause unintended and difficult to fix CSS collisions.

CSS Modules

CSS modules transforms your CSS files from being globally scoped to being component scoped, which means that the CSS you import into your component only affects the component you've imported that CSS file into. To transform a CSS file into a CSS modules file, simply change the file name from <file name>.css to <file name>.module.css. CSS modules also works with sass. In order to use CSS modules in your app, make the following changes.

//Importing Regular CSS
import './styles.css';

//Using Regular CSS
<div className="container">
<h3>Hi There!</h3>
</div>;
//Importing CSS Modules
import styles from './styles.module.css';

//Using CSS Modules
<div className={styles['container']}>
<h3>Hi There!</h3>
</div>;

The difference in your JavaScript files is that you treat your CSS file as an object and access the class names the same way you would access an object's attributes, but that's it! Your CSS files remain the same and now there's no need to worry about annoying CSS collisions.

Installation

CSS modules comes pre-installed with Create React App (sometimes referred to as CRA).

Documentation

Documentation


Styled Components

Using styled components is a bit more tricky.

Why Use Styled Components

  • Write CSS in JS alongside HTML in JS, but don't have to inline the CSS
  • Keeps track of which components are rendered on a page and injects their styles and nothing else, fully automatically.
  • You never have to worry about duplication, overlap or misspellings. No need to keep track of class names.
  • Adapting the styling of a component based on its props or a global theme is simple and intuitive without having to manually manage dozens of classes.

Installation

npm i styled-components

Trying It Out

In app.js,

const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;

const Wrapper = styled.section`
padding: 4em;
background: papayawhip;
`;

render(
<Wrapper>
<Title>Hello World!</Title>
</Wrapper>
);

The naming is as intuitive as it seems — Title is an <h1> tag, but with some styles associated with it. Wrapper is a div, but with some styles associated with it. To add style, we no longer need to inline, we can simply change the definition of Title or Wrapper.

Passing Arguments

const Button = styled.button`
background: ${(props) =>
props.primary === true ? 'palevioletred' : 'white'};
color: ${(props) => (props.primary ? 'white' : 'palevioletred')};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;

render(
<div>
<Button>Normal</Button>
<Button primary={true}>Primary</Button>
</div>
);

Documentation

styled-components

Component Libraries

A UI component library is a set of ready-made UI components such as buttons, inputs, dialogs, and so on. They serve as building blocks for layouts. Thanks to their modular nature, we can arrange components in many different ways to achieve unique effects. Each library has a distinctive look and feel, but most of them offer theming, and their components are customizable to a certain degree.

Popular component libraries for React include React-Bootstrap, Theme UI, and Chakra UI. We'll be focusing on Chakra UI.

Libraries are made for one reason – to make developers’ lives easier. UI component libraries are no different in their purpose.

Benefits

  • Speed – incorporating a UI component library can have a great impact on development speed. Instead of creating each element from scratch, we simply mix and match the existing components. Even if some additional customization is required, it tends to be much quicker than writing styles on our own.
  • Ease of use – components are meant to be easy to use. Established libraries are well-organized and have good documentation that is easy to follow. Very often, it’s enough just to copy and paste code snippets to make it work.
  • Attractive look – components look great without requiring any extra effort from the developer; they’re carefully designed by professionals.
  • Compatibility – ensuring cross-browser and cross-device compatibility is one of the biggest challenges of frontend development. UI component libraries provide compatibility out of the box, which makes everything so simple!
  • Accessibility – good libraries adhere to accessibility guidelines, so developers don’t even need to think about it.

Drawbacks

  • Generic look – your app’s look and feel will be determined by the library’s style in most parts. This is not necessarily an issue, but you need to make sure that this is acceptable for your project.
  • Change of design direction – It’s very important that designers understand and stick to components offered by the library. If the direction changes towards a custom solution, it will become increasingly difficult and time-consuming to overwrite the initial implementation.
  • New team members – every developer and designer needs to be familiar with the particular library and its implementation (though this point is true for any design system and any project).
  • Bundle size – UI component libraries tend to be big, so it’s probably not the best idea to engage them in tiny projects.

Chakra-UI

Chakra UI is component library based on a few principles:

  • Style Props: All component styles can be overridden or extended via style props to reduce the use of css prop or styled(). Compose new components from Box.

  • Simplicity: Strive to keep the component API fairly simple and show real world scenarios of using the component.

  • Composition: Break down components into smaller parts with minimal props to keep complexity low, and compose them together. This will ensure that the styles and functionality are flexible and extensible.

  • Accessibility: When creating a component, keep accessibility top of mind. This includes keyboard navigation, focus management, color contrast, voice over, and the correct aria-* attributes.

  • Dark Mode: Make components dark mode compatible. Use useColorMode hook to handle styling. Learn more about dark mode.

  • Naming Props: We all know naming is the hardest thing in this industry. Generally, ensure a prop name is indicative of what it does. Boolean props should be named using auxiliary verbs such as does, has, is and should. For example, Button uses isDisabled, isLoading, etc.

For more info on Chakra versus other component libraries, check out their docs.

Installation

yarn add @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^5

Setup Provider

import * as React from 'react';

// 1. import `ChakraProvider` component
import { ChakraProvider } from '@chakra-ui/react';

function App({ Component }) {
// 2. Use at the root of your app
return (
<ChakraProvider>
<Component />
</ChakraProvider>
);
}

Incorporating

Live Editor
Result
Loading...

React-Bootstrap

React-Bootstrap is a complete re-implementation of the Bootstrap components using React. It has no dependency on either bootstrap.js or jQuery. If you have React setup and React-Bootstrap installed, you have everything you need.

Methods and events using jQuery are done imperatively by directly manipulating the DOM. In contrast, React uses updates to the state to update the virtual DOM. In this way, React-Bootstrap provides a more reliable solution by incorporating Bootstrap functionality into React's virtual DOM.

Installation

yarn add react-bootstrap bootstrap

Incorporating Bootstrap

import React, { Component } from 'react';
import Alert from 'react-bootstrap/Alert';

const Example = () => {
return (
<Alert dismissible variant="danger">
<Alert.Heading>Oh snap! You got an error!</Alert.Heading>
<p>Change this and that and try again.</p>
</Alert>
);
};

export default Example;

Don't worry if you don't get what's going on here — you don't need to remember Bootstrap (and by extension ReactBootstrap!) Whatever you don't understand in the above block of text, google it!

Documentation

React-Bootstrap


Other Libraries (Optional)

There are many component libraries I haven't covered, and oftentimes larger projects and companies may create their own to fit their needs. I've included some other popular component libraries below. No component library is necessarily better than another, and each has pros and cons.

Material-UI: A popular React UI framework

Google's component library. The main complaint to using Material UI is that it's hard to customize and can thus leave some sites using a large amount of Material UI looking too much like something from Google.

Ant Design - The world's second most popular React UI framework

A library developed by the Chinese tech giant Alibaba.

Semantic UI

Lightweight CSS frameworks such as Tailwind or Pure can also be considered as viable alternatives to a component library. They offer a minimal, less opinionated CSS foundation that will help you speed up development, while at the same time allowing tremendous flexibility.

Package Managers (Optional)

What is NPM?

npm informally stands for node package manager. npm is a package manager for JavaScript and is the default package manager for Node.

It consists of a command-line client, also called npm, and an online database of public and paid-for private packages called the npm registry.

npm

In order to install a package to use in your react project using npm, you run npm install <name of package> in the root directory of your react app. This adds the package as a dependency in package.json.

It is important to note that the package.json is just a list of tools you are using. It doesn't do anything except to list them. The real magic happens in the node_modules folder, where the packages are stored for you.

What is Yarn?

Yarn is a package manager released by Facebook in 2016 and is a common alternative to NPM.

Home

To install a package using yarn, run yarn add <name of package> in the root directory. **

NPM vs Yarn

And now, to have an age-old discussion — which one should you use?

In practice, there is no real difference between the effects of using NPM and yarn, as both package managers work perfectly fine.

Back in the day, npm used to be super buggy, so yarn was created as a solution to NPM's deficiencies. These days the people behind npm were able to fix the main complaints people had against NPM, but many people and companies still prefer yarn as a byproduct of that era. There are definitely still advantages to using yarn, such as:

  • Installing packages using yarn tends to be faster than installing packages with npm
  • There are some slight security advantages to using yarn

but there are no other drastic differences between the two package managers. I personally use yarn and recommend yarn to others starting projects, but the choice is entirely up to you. Make sure you don't use both, however, because that can cause some problems on the backend when attempting to host your app.

If you are using npm, you should see a package-lock.json in your app. If you are using yarn, you should see a yarn.lock in your app.

React Router

We've learned about state, hooks, and components, and now we can build a nice single-page web app (commonly referred to as a SPA).

But now what? Most websites have multiple pages and some even have dynamic routes, routes that use data to determine what's shown to the user.

Think about youtube. The homepage showing you all the suggestions of videos you might want to see is youtube.com. At the same time, however, each video must have its own route. Instead of having some poor intern create a route for each video on youtube manually, we can use dynamic routing to display countless similar pages by using a variable in the URL that's determined by the video or piece of content that was clicked on. We can then go to that page and pull the variable from the URL to determine which piece of content to display.

To be doing all this, we'll be using React Router. React Router is by far the most popular client-side router in the React community. It is mature, being used by big companies, and battle-tested at large scales. It also has a lot of really cool capabilities, some of which we'll examine here.

More on Routing

If you want to learn more about client-side routing and its alternative, server-side routing, check out this resource.

Documentation

React router documentation can be found here.

Installation

yarn add react-router-dom

Why use Routers?

Why use a React Router at all? Didn't the <a> work? It did but with a flaw: every link you clicked would end up in the browser navigating to a whole new page which means React would totally reload your entire app all over again. With Routers, and its special features, it can intercept this and just handle that all client-side. Much faster and a better user experience. The base URL and page never actually change.

React Router is, at the end of the day, a conditional rendering wrapper that only shows you the component associated with the link you've selected or are on.

Basic Routing

Let's implement a basic example of routing.

Introducing a New Tag Introducing a new tag: <nav> ! It is intended to

wrap major navigation blocks on your webpage. Here's more information about it.

import React from 'react';
import { BrowserRouter, Switch, Route, Link } from 'react-router-dom';

const App = () => {
return (
<BrowserRouter>
<div>
{/* The HTML */}
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/users">Users</Link>
</li>
</ul>
</nav>

{/* The React Router that makes the HTML above work */}
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/users">
<Users />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</div>
</BrowserRouter>
);
};

const Home = () => <h2>Home</h2>;
const About = () => <h2>About</h2>;
const Users = () => <h2>Users</h2>;

export default App;

Dynamic Routing

What if we have too many routes to write down manually? For example, what if we want a route for every listing on Airbnb? Here's where dynamic routing comes into play.

We can set our routes to use a variable by simply putting a colon in front of the term we want to use as a variable in our route. See the example below where we use :id in our route, creating a variable id that is then later accessed.

import React from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useParams,
} from 'react-router-dom';

function DynamicRouting() {
const data = [
{ name: 'Netflix', route: 'netflix' },
{ name: 'Zillow', route: 'zillow' },
{ name: 'Yahoo', route: 'yahoo' },
{ name: 'Instagram', route: 'instagram' },
];

return (
<Router>
<div>
<h2>Accounts</h2>

<div>
{data.map((item) => (
<Link to={`/${item.route}`}>
<div>
<h3>{item.name}</h3>
</div>
</Link>
))}
</div>

<Switch>
<Route path="/:id" children={<Child />} />
</Switch>
</div>
</Router>
);
}

function Child() {
let { id } = useParams();

return (
<div>
<h3>{id}</h3>
</div>
);
}

export default DynamicRouting;

As you can see, we use /:id as a variable route, or a dynamic route rather than a static route. There is no path /id (unless we want there to be), rather, the id variable is a URL parameter, or param. We specifically the children of the /:id route in our <Route> tag, and this component is what will be rendered to the user. As we can see above, the Child component changes what it displays depending on the route that it is displayed from. We can grab the id param from the URL by using the useParams hook as shown in the example, then proceed to use it as a variable in our component.

To add an example, say you were looking at a list of cards in airbnb, each with basic listing info on them. When you want to learn more about a listing, you clock on that card, which then takes you to another page with more complete info about that listing. Relating that action to dynamic routing, each listing has a unique id. The browser takes you to a URL with that id when you click on a card. In this way Airbnb implements dynamic routing, setting the URL to the id of the listing you chose. Next, the router realizes that it has a dynamic route (/listing/12345 might match to /listing/:id for example) and renders that route's assigned component, which we'll call Listing. Listing then uses hooks such as useParam to determine the id of the requested listing, then queries Airbnb's database to get the info for that listing before displaying it to the user.

Complex Routing — A Practical Example (Optional)

I've included an example from the WDB website below.

import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';

import Navbar from './components/navbar';
import Footer from './components/footer';

import Landing from './pages/landing';
import About from './pages/about';
import Initiatives from './pages/initiatives';
import Error from './pages/error';

import './App.scss';

function App() {
return (
<Router>
<Switch>
<Route exact path="/">
<Navbar landing />
</Route>
<Route path="*">
<Navbar />
</Route>
</Switch>

<Switch>
{/* About Us */}
<Route path="/about">
<About />
</Route>

{/* Initiatives */}
<Route path="/initiatives">
<Initiatives />
</Route>

{/* Landing Page */}
<Route exact path="/">
<Landing />
</Route>

{/* 404 no page found */}
<Route path="*">
<Error />
</Route>
</Switch>

<Footer />
</Router>
);
}

export default App;

Let's take a closer look at the following snippet from the above

<Switch>
<Route exact path="/">
<Navbar landing />
</Route>
<Route path="*">
<Navbar />
</Route>
</Switch>

The exact prop tells our router to only show the landing version of the navbar when there are no extensions to the URL, which makes sense since we want our landing page to be the default page. Using * as the path for our regular navbar tells the router that for all other pages, render the regular navbar.

info

Using a single variable as a prop (such as exact or landing above) is equivalent to setting exact={true} or landing={true} as a prop. Thus we just use exact or landing instead of the whole statement as shorthand.

Conclusion

This lesson is all about helping you realize that the React universe is massive, collaborative, and full of tools that make writing React faster and easier. There are many more tools to the React ecosystem that we were unable to touch on today and that you may not need at any point in the course, but keep in mind that if you're trying to solve a general problem or create something specific, there's probably an npm package for that.


Contributors