SPAs with Multiple Pages

React Router BitCoin PriceFinder
Purpose of this lesson is to Build A Crypto Price Discovery App and learn
- How to setup react router
- How to create Router, Route, Link and Routes components
- How to pass router props
- How to use URL Params
The Problem
We are often used to making websites with several "pages" which would be split across several html delivered statically or rendered by templates on a server. When making a React app, the application is a single page with one html file. We can have components conditionally render to make the illusion of pages but it doesn't quite feel as intuitive as using a tags to link to different html files.
What the React-Router library does is allow us to define components that render based on the url in the address bar. We link to them with Link components which feel similar to the a tags we are used to. It allows to create a single page application in a way that feels like a multi-page application.
The Link component in React Router enables SPAs to imitate multi-page applications by intercepting navigation events, updating the browser's URL and history, and rendering the appropriate components based on the current route. This creates the appearance of navigating through multiple pages while maintaining the performance benefits and smooth user experience of an SPA.
Setup
In your React folder do the following
- run command
npx create-react-app cryptoprices
- cd into the cryptoprices folder
Delete the src folder
- go to to the terminal
rm -rf src
Your Instructor will explain what this command does- recreate and enter the new src folder with
mkdir src && cd src
- add back
index.js
,App.js
,style.css
withtouch index.js App.js styles.css
get started
- run
npm install react-router-dom
- run
npm start
to begin development server
Setting Up Router
The first component we'll explore is BrowserRouter which is underneath the hood a context provider allowing all the features of router to be available to its children. We want all of our application to have the router features so we'll wrap the App component in index.js and to make it more semantic we'll rename the component Router.
index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import "./style.css";
import App from "./App";
//IMPORT BrowserRouter and rename it to Router
import { BrowserRouter as Router } from "react-router-dom";
//Wrap the App Component with the Router component to enable the router features
ReactDOM.render(
<StrictMode>
<Router>
<App />
</Router>
</StrictMode>,
document.getElementById("root")
);
Components vs Pages
A common convention is to create two folders, components and pages. Any component that is used as a piece of UI goes in the components folder, any component meant to act as a "page" of the website goes in pages.
- create a components and pages folder in
src
- create a Currencies.js, Main.js, Price.js file in the pages folder
- create the component boilerplate in each component
Main.js
export default function Main (props){
return <h1>This is the Main Component</h1>;
};
Currencies.js
export default function Currencies (props) {
return <h1>This is the Currencies Component</h1>;
};
Price.js
export default function Price (props) {
return <h1>This is the Price Component</h1>;
};
Creating Our Routes
Now we will will import the Route & Routes component into App, this will allow us define which of our components should render depending on the URL, we'll also import our pages for our routes.
App.js
//Import route and our components
import { Route, Routes } from "react-router-dom";
import Currencies from "./pages/currencies";
import Main from "./pages/Main";
import Price from "./pages/Price";
export default function App () {
// We will use the Route component to specify each route
return (
<div className="App">
<Routes>
<Route path="/" element={<Main/>}/>
<Route path="/currencies" element={<Currencies/>}/>
<Route path="/price" element={<Price/>}/>
</Routes>
</div>
);
}
Right now only the Main component is rendering cause we are on the main page, "/". To change the URL bar, we need some links so lets create a navigation.
Navigation
In your components folder create a Nav.js
components/Nav.js
import { Link } from "react-router-dom";
export default function Nav (props){
return (
<div className="nav">
<Link to="/">
<div>CRYPTO PRICES</div>
</Link>
<Link to="/currencies">
<div>CURRENCIES</div>
</Link>
</div>
);
};
Next add the following styles to styles.css
.nav {
display: flex;
justify-content: space-between;
background-color: black;
color: white;
padding: 15px;
font-size: 2em;
}
.nav a {
color: white;
text-decoration: none;
}
import the Nav component into App.js
//Import route and our components
import { Route, Routes } from "react-router-dom";
import Currencies from "./pages/Currencies";
import Main from "./pages/Main";
import Price from "./pages/Price";
import Nav from "./components/Nav";
export default function App () {
// We will use the Route component to specify each route
return (
<div className="App">
<Routes>
<Route path="/" element={<Main/>}/>
<Route path="/currencies" element={<Currencies/>}/>
<Route path="/price" element={<Price/>}/>
</Routes>
</div>
);
}
A Few things to notice:
- The function of the link tags is to change the URL bar to match the "to" prop, look at the change in the URL bar when you click on them. The reason we don't use an a tag is cause clicking an a tag triggers the browser to make a request and refresh the page which will break router (cause there is no server to respond to the browsers request, the url is merely a simulation of multiple pages).
//Import route and our components
import { Route, Routes } from "react-router-dom";
import Currencies from "./pages/Currencies";
import Main from "./pages/Main";
import Price from "./pages/Price";
import Nav from "./components/Nav";
export default function App () {
// We will use the Route component to specify each route
return (
<div className="App">
<Nav />
<Routes>
<Route path="/" element={<Main/>}/>
<Route path="/currencies" element={<Currencies/>}/>
<Route path="/price" element={<Price/>}/>
</Routes>
</div>
);
}
Params
We are going to soon build out our currencies component which will allow us to select which currencies price we'd like to see. We will do this by injecting a variable in our Price routes path, so edit the Price route like so...
<Route path="/price/:symbol" element={<Price/>}/>
The :symbol part is a URL Param, a variable in the url. Whatever is in that spot in the path will be accessible by using the useParams hook.
The Currencies Component
In this component we will be doing the following
- Creating an array of the currencies our app can find prices for
- Looping over that array to generate a link for each one to the price route
- The currency symbol should be placed in the :symbol part of the URL
Currencies.js
import { Link } from "react-router-dom";
export default function Currencies (props) {
const currencies = [
{ name: "Bitcoin", symbol: "BTC" },
{ name: "Litecoin", symbol: "LTC" },
{ name: "Ethereum", symbol: "ETH" },
{ name: "Ethereum Classic", symbol: "ETC" },
{ name: "Stellar Lumens", symbol: "XLM" },
{ name: "Dash", symbol: "DASH" },
{ name: "Ripple", symbol: "XRP" },
{ name: "Zcash", symbol: "ZEC" },
];
return (
<div className="currencies">
{currencies.map((coin) => {
const { name, symbol } = coin;
return (
<Link to={`/price/${symbol}`}>
<h2>{name}</h2>
</Link>
);
})}
</div>
);
};
Notice when we click any of the links it takes us to the price component, use the React devTools to look for the router props and you should be able to find the value of the symbol param in there.
The Price Component
Before we create this component take a moment to get your FREE Api key from coinapi.io. Keep in mind you can only make 100 requests per day with your free apiKey.
Once you have your api key here is what we will do:
- store the apikey and currency symbol in different variables
- use the useEffect hook to make an api call
- interpolate the apikey and symbol in the fetch URL
- save the resulting data in state and render it
- loaded and loading function for rendering the data if exists
Price.js
import {useState, useEffect} from "react";
import {useParams} from "react-router-dom"
export default function Price (props) {
// Our api key from coinapi.io
const apiKey = "YOUR API KEY";
// Grabbing the Currency symbol from the URL Params
const params = useParams()
const symbol = params.symbol
// Using the other two variables to create our URL
const url = `http://rest.coinapi.io/v1/exchangerate/${symbol}/USD?apikey=${apiKey}`;
//state to hold the coin data
const [coin, setCoin] = useState("null");
//function to fetch coin data
const getCoin = async () => {
const response = await fetch(url);
const data = await response.json();
setCoin(data);
};
const getCoin = async () => {
try{
const response = await fetch(url);
const data = await response.json();
setCoin(data);
} catch(e){
console.error(e)
}
};
// useEffect to run getCoin when component mounts
useEffect(() => {
getCoin();
}, []);
// loaded function for when data is fetched
const loaded = () => {
return (
<div>
<h1>
{coin.asset_id_base}/{coin.asset_id_quote}
</h1>
<h2>{coin.rate}</h2>
</div>
);
};
// Function for when data doesn't exist
const loading = () => {
return <h1>Loading...</h1>;
};
// if coin has data, run the loaded function, otherwise, run loading
return coin && coin.rate ? loaded() : loading();
};
Your App Should now be working! Voila!
More on the Link Component
The Link
component in React Router is a fundamental element that enables Single Page Applications (SPAs) to mimic the behavior of multi-page applications. It works by providing a declarative way to navigate between different components or views within an SPA without causing a full page reload.
Under the hood, the Link component generates an anchor tag (<a>
), but instead of relying on the traditional browser behavior of requesting a new page from the server (which for us doesn't exist), it intercepts the click event and updates the browser's URL and history using the HTML5 History API. This allows the application to maintain a consistent user experience and the illusion of navigating through multiple pages.
React Router listens for changes in the browser's history and matches the current URL with a defined route. When a match is found, the corresponding component is rendered, effectively swapping the content on the page without the need for a full reload. This seamless navigation is a key feature of SPAs, providing faster load times and smoother user experiences.