Enough talk, let's implement React Router!
- Clone this repo and
cd
into it - checkout the
in-class
branch npm i
npm start
- open your text editor
This application will provide us with a Main
landing page as well as 3 routes to pages containing:
- Unicorns
- Puppies
- Sharks
Additionally we will add a dynamic route to dig deeper into a specific creatures cards.
First let's install react-router-dom
npm i --save react-router-dom
Next let's go import it and wrap it around our main entry point App
:
//index.js
import { BrowserRouter } from 'react-router-dom'
const router = (
<BrowserRouter>
<App />
</BrowserRouter>
)
ReactDOM.render(router, document.getElementById('root'));
Next we will go to our App.js
file and begin constructing the routes for our application. We'll need to import some additional pieces from the library.
//App.js
import { Route, NavLink } from 'react-router-dom'
Now let's build a header to persist on all views. We will use NavLink
so we can take advantage of the activeClassName
attribute.
It comes with a default class of .active
so we can either use that without defining it, or define a new name.
export default class App extends Component {
render() {
return (
<div className='App'>
<div className='header-section'>
<header>
<NavLink to='/unicorns' className='nav'> Unicorns </NavLink>
<NavLink to='/puppies' className='nav'> Puppies </NavLink>
<NavLink to='/sharks' className='nav'> Sharks </NavLink>
</header>
</div>
</div>
);
}
}
If you click on these links now, you should see the URL change to the routes we told each NavLink to route to
.
Next we need to define a Home
route for when users first arrive to the app (or when the path='/'
). For now we'll just do a basic welcome message:
//Home.js
import React from 'react';
const Home = () => {
return (
<div>
<h1>Welcome!</h1>
<h4>Click on the links above to see a variety of creatures</h4>
</div>
)
}
export default Home;
Now let's define the route:
//App.js
<Route path='/' component={Home} />
Next we need to define those routes and tell it which components to render. Take 10 mintues and see if you can get the /unicorns
Route working by displaying <h1> Unicorns! </h1>
.
hint: You'll probably need to create a new component to render when on the /unicorns
route
Here's how we can do it using the component
render method on a Route
:
//App.js
<Route path='/unicorns' component={Unicorns} />
At this point clicking a header link (NavLink) should change the URL and render the component associated with that Route...however, we're still seeing the Home
component above, what's with that?
This is where we need to use the exact
attribute on a Route
//App.js
<Route exact path='/' component={Home} />
What we're saying by adding this attribute is that only when the path is EXACTly '/'
do we want to render the Home
component.
Now that we have routes defined, and a template of a component, let's get something more fun displaying. Go to your Unicorns
component and replace what you have with this code:
//Unicorns.js
import React from 'react';
import './image-display.css';
import unicornData from './data/unicorn-data'
const Unicorns = () => {
const displayUnicorns = unicornData.map((unicorn, i) => <img src={unicorn.image} className='app-img' key={unicorn.id}/>)
return (
<div className='image-display'>
<h1>Unicorns!</h1>
{displayUnicorns}
</div>
)
}
export default Unicorns;
We should now be able to see a bunch of unicorns displaying on the page!
Take some time to mimic these steps for Puppies
and Sharks
so that each respective route shows images of their respective creatures.
A few things to keep in mind:
- Each component will share classNames and need the
image-display.css
file - Each component will need their respective data:
unicorn-data.js
puppy-data.js
shark-data.js
Let's recap where we're at:
- We have defined four Routes:
/
/unicorns
/puppies
/sharks
- Each route renders a different component
- The
Home
route requires anexact
attribute because all of the routes contain/
Hopefully this seems pretty straight forward so far, but what if we want to go a level deeper? When a user clicks on an image, we want to send them to a new view where they can see information specific to that creature only. This is where we get into dynamic routing.
Currently we have 9 creatures per component and we want to be able to link to a specific view for each creature. One way we could do this is to create a route for each creature...something like:
<Route to='/unicorns/1' />
<Route to='/unicorns/2' />
<Route to='/unicorns/3' />
<Route to='/unicorns/4' />
...
This would be incredibly inefficient. Instead, we can use the render
attribute within our Route
to dynamically match the id of the URL with the matching ID within our data.
To signify a dynamic route, you simply add a colon in front of the parameter you're dynamically changing.
path='/unicorns/:id'
Let's focus just on unicorns for now. Here are the steps we're working through:
- A user clicks a specific image
- We
Link
to a dynamic route where the end of the URL matches the ID of that specific unicorn - Based on the ID in the URL, we pass through data specific to that matching unicorn
So, if our first unicorn's data looks like this:
{
id: 1,
name: 'Chuck',
image: img1,
type: 'unicorns',
bio: bio1
}
We want to redirect to /unicorns/1
Then, we want to define a Route
that looks at the parameter in the URL and passes the specific matching data into something we can render.
Let's poke the bear a little bit. Paste this route into your App.js
file:
//App.js
<Route path='/unicorns/:id' render={({ match }) => {
console.log(match)
return (
<div>New Unicorn Route!</div>
)
}} />
Now visit this URL and open up your console: http://localhost:3000/unicorns/1
First thing we should see is that all of our unicorns are still showing, why do you think this is?
It's because we didn't specify the exact
attribute in our /unicorns
route, so that route sees the URL is /unicorns/1
, considers it a match and renders any components that match. Let's fix this for all three components:
//App.js
<Route exact path='/unicorns' component={Unicorns} />
<Route exact path='/sharks' component={Sharks} />
<Route exact path='/puppies' component={Puppies} />
Ok back to business!
We passed through the match
prop and console logged it, let's take a look. Notice that the params
property contains an object with our defined paramter id
, and it is equal to the ID we linked to in the URL!
Now all we have to do is modify our route to render the correct data.
See if you can write some codes to render the correct data based on the ID in the URL. Focus just on unicorns for now.
Hints:
- You will need to bring in the data from the
unicorn-data.js
file - You may want to use the
CreatureDetails
component already set up for you - Focus first on seeing the data when you type in the URL manually, we'll set up the click next
Here's the code:
<Route path='/unicorns/:id' render={({ match }) => {
const { id } = match.params
const creature = unicornData.find(uni => uni.id === parseInt(id))
if (creature) {
return <CreatureDetails {...creature} />
}
}} />
Now if we visit http://localhost:3000/unicorns/1
we should see a view specifically for Unicorn 1!
See if you can modify your Unicorn
component so that each image can be clicked and Link
to the correct Route / path / URL.
Here's a simple addition to Unicorn.js
.
First we import { Link } from 'react-router-dom'
Then we just wrap what we returned before with a <Link>
as such:
const displayUnicorns = unicornData.map((unicorn, i) => {
return (
<Link to={`/unicorns/${unicorn.id}`} key={unicorn.id}>
<img src={unicorn.image} className='app-img' />
</Link>
)
})
If we really wanted to be efficent, we could turn this into a separate component since our Puppies
and Sharks
components are likely to operate the same way. So instead we can just render the component and pass through all of the data as such:
// Unicorn.js
import React from 'react';
import './image-display.css';
import unicornData from './data/unicorn-data.js';
import ImageCard from './ImageCard';
const Unicorns = () => {
const displayUnicorns = unicornData.map((unicorn, i) =>
<ImageCard {...unicorn} key={unicorn.id} className='app-img'/>
)
return (
<div className='image-display'>
<h1>Unicorns!</h1>
{displayUnicorns}
</div>
)
}
export default Unicorns;
// ImageCard.js
import React from 'react';
import './image-display.css';
import { Link } from 'react-router-dom';
const ImageCard = ({ name, bio, image, id, type }) => {
return (
<Link to={`${type}/${id}`}>
<img src={image} className='app-img' />
</Link>
)
}
export default ImageCard;
And that's it! Go ahead and work on setting up dynamic routes for the other two components!