How To Use React Router With React
Tags: react, javascript, webdev, html
Published: 2019-12-28 03:41:05

**Subscribe to my email list now at http://jauyeung.net/subscribe/**

**Follow me on Twitter at https://twitter.com/AuMayeung**


If you want to make a single-page app with React, you need to add routing to your app. [React Router](https://www.npmjs.com/package/react-router) is the most popular routing library for React apps. To use it, you have to add the library via npm.

In this piece, I will create an app with React and React Router in the simplest way possible.

To create a new React app, use the `create-react-app` code generator made by the developers of React. Here are the [README](https://github.com/facebook/create-react-app) and [full documentation](https://facebook.github.io/create-react-app/).

The app that will be created is an app that displays data from the [Dog API](https://dog.ceo/dog-api/documentation/).

To create the app, run `npx create-react-app` and follow the instructions, you will get a new app. Then, you are ready to install React Router.

To install it, run `npm i react-router-dom`. After that, install @material-ui/core and [axios](https://www.npmjs.com/package/axios) by running `npm i @material-ui/core axios`. Material UI provides a Material Design look to our app and Axios is an HTTP client which works at client-side apps.

In `index.js`, we have:

```
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { breedsReducer, imagesReducer } from './reducers';
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import { combineReducers } from 'redux'
const dogApp = combineReducers({
  breeds: breedsReducer,
  images: imagesReducer
})

const store = createStore(dogApp)ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>
, document.getElementById('root')
);serviceWorker.unregister();
```

First, we add reducers to store state in a centrally available location. We make a file called `reducer.js`:

```
import { SET_BREEDS, SET_IMAGES } from './actions';

function breedsReducer(state = {}, action) {
 switch (action.type) {
    case SET_BREEDS:
      state = JSON.parse(JSON.stringify(action.payload));
      return state;
    default:
      return state
   }
}

function imagesReducer(state = [], action) {
 switch (action.type) {
    case SET_IMAGES:
      state = JSON.parse(JSON.stringify(action.payload));
      return state;
    default:
      return state
  }
}

export { breedsReducer, imagesReducer };
```

In `actions.js`, we add these constants for our [Redux](https://redux.js.org/) actions:

```
const SET_BREEDS = 'SET_BREEDS';
const SET_IMAGES = 'SET_IMAGES';export { SET_BREEDS, SET_IMAGES };
```

In `actionCreators.js`, we add:

```
import { SET_BREEDS, SET_IMAGES } from './actions';

const setBreeds = (breeds) => {
  return {
    type: SET_BREEDS,
    payload: breeds
  }
};

const setImages = (images) => {
  return {
    type: SET_IMAGES,
    payload: images
  }
};

export { setBreeds, setImages };
```

In `app.js`, we change the default code to:

```
import React, { useState, useEffect } from "react";
import './App.css';
import { setBreeds } from './actionCreators';
import { connect } from 'react-redux';
import { Router, Route, Link } from "react-router-dom";
import BreedsPage from './BreedsPage';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import Drawer from '@material-ui/core/Drawer';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import { makeStyles } from '@material-ui/core/styles';
import { createBrowserHistory as createHistory } from 'history'
const history = createHistory();
const axios = require('axios');
const useStyles = makeStyles(theme => ({
  root: {
    flexGrow: 1,
  },
  menuButton: {
    marginRight: theme.spacing(2),
  },
  title: {
    flexGrow: 1,
  },
}));

function App({ setBreeds }) {
  const classes = useStyles();
  const [initialized, setInitialized] = useState(false);
  const [state, setState] = useState({
    openDrawer: false }); const titles = {
     '/': 'Dog App',
     '/breeds': 'Get Images By Breed - Dog App',
     '/subbreeds': 'Get Images By Breed or Sub-Breed - Dog App'
   }

   history.listen((location, action) => {
     document.title = titles[location.pathname];
     setState({ openDrawer: false });
   }); 

   const toggleDrawer = (open) => event => {
   if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
      return;
    }
    setState({ openDrawer: open });
   }; 

   const links = {
    'Home': '/',
    'Breeds': '/breeds',
    'Sub-Breeds': '/subbreeds',
   } const getBreeds = () => {
    setInitialized(true);
    axios.get('https://dog.ceo/api/breeds/list/all')
    .then((response) => {
      setBreeds(response.data.message);
    })
    .catch((error) => {
      console.log(error);
    })
    .finally(() => {
    });
   } 

   useEffect(() => {
     if (!initialized) {
       getBreeds();
     }
   }); 

   return (
      <div className="App">
        <Router history={history}>
          <Drawer anchor="left" open={state.openDrawer} onClose=       {toggleDrawer(false)}>
            <List>
               <ListItem button>
                 <h2><b>Dog App</b></h2>
               </ListItem>
               {Object.keys(links).map((text) => (
                  <ListItem button key={text}>
                    <Link to={links[text]}>
                      <ListItemText primary={text} />
                   </Link>
                </ListItem>
                ))}
            </List>
          </Drawer>
           <AppBar position="static">
             <Toolbar>
               <IconButton edge="start" className={classes.menuButton} color="inherit" aria-label="Menu" onClick={toggleDrawer(true)}>
                 <i className="material-icons">menu</i>
               </IconButton>
               <Typography variant="h6" className={classes.title}>Dog App</Typography>
             </Toolbar>
          </AppBar>
          <Route path="/breeds/" component={BreedsPage} />
        </Router>
       </div>
      );
}

const mapStateToProps = (state) => ({
  breeds: state.breeds
})

const mapDispatchToProps = (dispatch) => ({
  setBreeds: breeds => dispatch(setBreeds(breeds))
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(App);
```

The `<Route path=”/breeds/” component={BreedsPage} />` is where the route is defined. It must be inside `<Router history={history}></Router>`.

This is the routing part of our application. With this, we can go to the page when we type in `http://localhost: 3000/breeds`.

This block sets the title and hides the app drawer on the left when the route changes:

```
history.listen((location, action) => {
   document.title = titles[location.pathname];
   setState({ openDrawer: false });
});
```

We now create the pages for our apps which will use React Router for routing. First, we create a page for displaying breeds, we call it `BreedPage.js`.

The code will look like this:

```
import React from 'react';
import './BreedsPage.css';
import { setImages } from './actionCreators';
import { connect } from 'react-redux';
import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';
import { makeStyles } from '@material-ui/core/styles';
import ImagesBox from './ImagesBox';
const axios = require('axios');
const useStyles = makeStyles(theme => ({
  formControl: {
    margin: theme.spacing(1),
    width: '90vw',
  },}));function BreedsPage({ breeds, setImages }) {
  const classes = useStyles();
  const [state, setState] = React.useState({
    breed: '',
  });
  const [initialized, setInitialized] = React.useState(false);
  const handleChange = name => event => {
    setState({...state, [name]: event.target.value,});
    if (name == 'breed') {
      getImagesByBreed(event.target.value);
    }
  }; 

  const getImagesByBreed = (breed) => {
    axios.get(`https://dog.ceo/api/breed/${breed}/images`)
    .then((response) => {
      setImages(response.data.message);
    })
    .catch((error) => {
      console.log(error);
    })
    .finally(() => {
    });
  } 

  React.useEffect(() => {
    if (!initialized) {
      setInitialized(true);
      setImages([]);
    }
  }); 

  return (
    <div className="App">
      <h1>Get Images By Breed</h1>
      <form>
       <FormControl className={classes.formControl}>
         <InputLabel>Breed</InputLabel>
         <Select
           value={state.breed}
           onChange={handleChange('breed')}
         >
           {Object.keys(breeds || {}).map(b =>
             <MenuItem value={b} key={b}>{b}</MenuItem>
           )}
         </Select>
       </FormControl>
       <ImagesBox></ImagesBox>
      </form>
    </div>
  );
}

const mapStateToProps = state => {
 return {
    breeds: state.breeds,
    images: state.images
  }
}

const mapDispatchToProps = dispatch => ({
  setImages: images => dispatch(setImages(images))
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(BreedsPage);
```