Today will be spent working on the front end in React. Today's goals are to get the routing working, integrate Bootstrap, and display a form.
Project Structure
Inside the src
folder are two folders titled components
and routes
, along with an index.js
file. The components
folder holds Nav.jsx
and the routes
folder holds App.jsx
, Home.jsx
, Survey.jsx
, and Dashboard.jsx
.
Basic Routing
// index.js
import ReactDOM from "react-dom/client";
import {
BrowserRouter,
Routes,
Route
} from "react-router-dom";
import App from "./routes/App";
import Survey from "./routes/Survey";
import Dashboard from "./routes/Dashboard";
import Home from "./routes/Home";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<BrowserRouter>
<Routes>
<Route path="/" element={<App />}>
<Route index element={<Home />} />
<Route path="survey" element={<Survey />} />
<Route path="dashboard" element={<Dashboard />} />
<Route path="*" element={<p>There's nothing here!</p>} />
</Route>
</Routes>
</BrowserRouter>
);
This sets up nested routing as follows:
- App is the top-level element and because all other routes are nested within "/", App serves as a template for the entire site.
- All other routes are defined relative to "/" because they're nested, so they do not include a leading slash in their route.
- the
index
route defines the content that will only be displayed when the user accesses the parent route (in this case, it's '/', which in my case is currentlylocalhost:3000
) path="*"
defines what should be shown when the user tries to access a URL with no matching route. Note that this does NOT return a 404 status code; I am going to ignore that problem for now.
// App.jsx
import { Outlet } from "react-router-dom";
import Nav from '../components/Nav';
export default function App() {
return (
<div>
<Nav />
<Outlet />
</div>
);
}
Currently, App serves as an extremely basic template: it renders the Nav component on every page, and that's it. The Outlet component is provided by React Router, and defines the spot where elements from nested routes will be rendered. If you've ever used a template engine or static site generator, it works something like {{ content }}
or {% block content %}{% endblock %}
.
// Nav.jsx
import { Link } from "react-router-dom";
export default function Nav() {
return (
<nav>
<Link to="/">Home</Link> |{" "}
<Link to="/survey">Survey</Link> |{" "}
<Link to="/dashboard">Dashboard</Link>
</nav>
)
}
Nav is currently just a few links to go from page to page. The Link element is provided by React Router and serves the same function as an <a>
tag. However, Link will not refresh the page but <a>
will.
// Home.jsx
export default function Home() {
return (
<h1>Home</h1>
)
}
// Survey.jsx
export default function Survey() {
return (
<h1>Survey</h1>
)
}
// Dashboard.jsx
export default function Dashboard() {
return (
<h1>Dashboard</h1>
)
}
Home.jsx
is the index route for "/", Survey.jsx
and Dashboard.jsx
are the elements to be rendered for their respective routes, nested inside "/" (as defined in index.js
).
This sets up the basic structure for the site and produces this rather unimpressive result:
You can see in the bottom left that the URLs are changing for each page, but the page doesn't refresh when the links are clicked. Although it doesn't look like much, this does provide the bones needed for the next steps.
Bootstrap
I am using Reactstrap to add Bootstrap to my project. This isn't the only way to add Bootstrap to React, but it does add Bootstrap components as React components, which I like. I have already installed Reactstrap and Bootstrap, so the only thing left to do to get it up and running is import the CSS by adding this import to index.js
:
import 'bootstrap/dist/css/bootstrap.css';
Bootstrap is working, so now it's time to start adding some components and styling.
The first thing I wanted to do was add a Bootstrap Navbar to the Nav component I had created, but I immediately ran into a couple of problems:
- Bootstrap also has a component named Nav which was conflicting with my function declaration. I could have give the Boostrap Nav an alias but I thought it was easier (and a little more semantic) to simply rename my Nav component to Header.
- The Link component from React Router will not track which page you're currently on, you have to use NavLink for that, but NavLink already exists in Reactstrap. I solved this by giving React Router's NavLink an alias:
import { NavLink as RRNavLink } from 'react-router-dom';
- The NavLink component provided by Reactstrap works like a regular
<a>
tag - that is, it refreshes the page when clicked. Plus, it is not aware of the page you're currently on and therefore can't highlight it in the nav, since it can't automatically add an 'active' class without that information. React Router's NavLink does have this information as I mentioned above, but directly using the NavLink from React Router means I lose access to the Bootstrap styling. What's needed is a way to get them to work together. Fortunately, one already exists: usetag={RRNavLink}
on the Bootstrap NavLink and then you can use it as a React Router NavLink while maintaining the Bootstrap styling.
// Header.jsx (previously Nav.jsx)
import { NavLink as RRNavLink } from 'react-router-dom';
import {
Navbar,
Nav,
NavItem,
NavLink
} from 'reactstrap';
export default function Header() {
return (
<header>
<Navbar>
<Nav pills>
<NavItem>
<NavLink tag={RRNavLink} to="/" activeClassName="active">Home</NavLink>
</NavItem>
<NavItem>
<NavLink tag={RRNavLink} to="/survey" activeClassName="active">Survey</NavLink>
</NavItem>
<NavItem>
<NavLink tag={RRNavLink} to="/dashboard" activeClassName="active">Dashboard</NavLink>
</NavItem>
</Nav>
</Navbar>
</header>
)
}
The next step is to add a bit of layout to the site. This was trivial, since the layout is currently so simple. I didn't add Col elements in App so that they can be added in nested elements as needed.
// App.jsx
import { Outlet } from "react-router-dom";
import { Container, Row } from 'reactstrap';
import Header from '../components/Header';
export default function App() {
return (
<Container>
<Row>
<Header />
</Row>
<Row>
<Outlet />
</Row>
</Container>
A Form
The last step for today is to add a form to the survey page. Reactstrap includes Bootstrap form components, and since I'm not hooking the form up to anything just yet, this is another easy task.
// Survey.jsx
import {
Row,
Col,
Form,
FormGroup,
Label,
Input,
FormText,
Button
} from 'reactstrap';
export default function Survey() {
return (
<Col>
<h1>Survey</h1>
<Form>
<Row>
<Col>
<FormGroup>
<Label for="exampleName">
Name
</Label>
<Input
id="exampleName"
name="name"
placeholder="Name"
type="text"
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="exampleEmail">
Email
</Label>
<Input
id="exampleEmail"
name="email"
placeholder="Email"
type="email"
/>
</FormGroup></Col></Row>
<Row>
<Col>
<FormGroup>
<Label for="examplePassword">
Password
</Label>
<Input
id="examplePassword"
name="password"
placeholder="Password"
type="password"
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="exampleConfirm">
Confirm
</Label>
<Input
id="exampleConfirm"
name="password"
placeholder="Type password again"
type="password"
/>
</FormGroup>
</Col>
</Row>
<Row>
<Col>
<FormGroup>
<Label for="exampleSelect">
Select
</Label>
<Input
id="exampleSelect"
name="select"
type="select"
>
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</Input>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="exampleSelectMulti">
Select Multiple
</Label>
<Input
id="exampleSelectMulti"
multiple
name="selectMulti"
type="select"
>
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</Input>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="exampleText">
Text Area
</Label>
<Input
id="exampleText"
name="text"
type="textarea"
/>
</FormGroup>
</Col>
</Row>
<FormGroup>
<Label for="exampleFile">
File
</Label>
<Input
id="exampleFile"
name="file"
type="file"
/>
<FormText>
This is some placeholder block-level help text for the above input. It's a bit lighter and easily wraps to a new line.
</FormText>
</FormGroup>
<FormGroup tag="fieldset">
<legend>
Radio Buttons
</legend>
<FormGroup check>
<Input
name="radio1"
type="radio"
/>
{' '}
<Label check>
Option one is this and that—be sure to include why it‘s great
</Label>
</FormGroup>
<FormGroup check>
<Input
name="radio1"
type="radio"
/>
{' '}
<Label check>
Option two can be something else and selecting it will deselect option one
</Label>
</FormGroup>
<FormGroup
check
disabled
>
<Input
disabled
name="radio1"
type="radio"
/>
{' '}
<Label check>
Option three is disabled
</Label>
</FormGroup>
</FormGroup>
<FormGroup check>
<Input type="checkbox" />
{' '}
<Label check>
Check me out
</Label>
</FormGroup>
<Button>
Submit
</Button>
</Form>
</Col>
)
}
}
This is just an example form provided by Reactstrap with some rows and columns added to play around with a nested layout, but it should be sufficient for this part of the project. Tomorrow I'll start actually using some of React's features.
NOTE: This is not a tutorial. I am learning how to build a MERN app and decided to record my progress. Although I'm doing my very best to make sure my code is correct, I may not always be following best practices. If you see any mistakes, feel free to contact me at l@abrocadabro.com or leave a comment. I would love to know what I can do better!