Author avatar

Kimaru Thagana

Handling Tabs Using Page URLs and React Router Doms

Kimaru Thagana

  • Sep 29, 2020
  • 13 Min read
  • 155 Views
  • Sep 29, 2020
  • 13 Min read
  • 155 Views
Web Development
Front End Web Development
Client-side Frameworks
React

Introduction

Tabs are critical elements for grouping similar data in web apps. However, most implementations of tabs in React use React state to maintain the active tab. This has the limitations of:

  • Inability to maintain the active tab on refreshing the page
  • A lot hassle to redirect to a specific tab

Learn how to maintain your active tab by using URL parameters and leveraging React Router Dom to overcoming these limitations.

This guide assumes that you are familiar with React, React Hooks, React Router Dom, Bootstrap, and Reactstrap.

Setup

Setup is split into two steps:

  • Setting Up the URL-Managed-Tabs React App
  • Installing Reactstrap

Setting Up the URL-Managed-Tabs React App

Use Create-React-App, a scaffold that lets you create React apps with no build configurations.

Ensure you have create-react-app installed on your machine. If not, you can install it by running the following:

1
npm install -g create-react-app
bash

Once it is installed, to create the app, run the following:

1
npx create-react-app url-managed-tabs
bash

The above command creates a React app with the name url-managed-tabs.

Navigate to your project's root directory.

1
cd url-managed-tabs
bash

Your folder structure should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
url-managed-tabs/    
    node_modules/
    public/
    src/   
    	App.css
        App.js
        App.test.js
        index.css
        index.js
        logo.svg
        serviceWorker.js
        setupTests.js
    package.json
    README.md
    yarn.lock

To start your app, run the following:

1
yarn start
bash

Install Reactstrap

First, install bootstrap, a pre-requisite for the Reactstrap package.

1
npm install --save bootstrap
bash

Then install the Reactstrap package.

1
npm install --save reactstrap
bash

Import Bootstrap CSS in the src/index.js file.

1
import 'bootstrap/dist/css/bootstrap.min.css';
jsx

Development

Development will be split into the following steps:

  • Create the Tabs
  • Install React-Router-Dom
  • Create a Route with an Optional Parameter using React Router Dom
  • Refactor the Tabs to Use the URL active_page Parameter

Create the Tabs

Open the App.js file. Clear everything inside.

Then, create an object called tabs, defining the title and content of all the tabs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import React from 'react';
import {Row, Col} from 'reactstrap';

function App() {
    const tabs = {
            "draft": {
                title: "Draft",
                content: (
                    <Row className="p-2">
                        <Col sm="12" className="p-2">
                            <h4 className="text-info">Draft Tasks</h4>
                        </Col>
                    </Row>
                )
            },
            "in_progress": {
                title: "In Progress",
                content: (
                    <Row className="p-2">
                        <Col sm="12" className="p-2">
                            <h4 className="text-primary">In Progress Tasks</h4>
                        </Col>
                    </Row>
                )
            },
            "completed": {
                title: "Completed",
                content: (
                    <Row className="p-2">
                        <Col sm="12" className="p-2">
                            <h4 className="text-success">Completed Tasks</h4>
                        </Col>
                    </Row>
                )
            }
        }


    return (
        <React.Fragment/>
    );
}

export default App;
jsx

Create the tabs by iterating through the tabs object. Use useState React hook to add activeTab property to the React State of the App Component. Initialize the activeTab property with your preferred tab property name, in this case, in_progress. Define toggle function to handle the change of activeTab on clicking the NavLinks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import React from 'react';
import {Row, Col} from 'reactstrap';

function App() {
    const tabs = {
        "draft": {
            title: "Draft",
            content: (
                <Row className="p-2">
                    <Col sm="12" className="p-2">
                        <h4 className="text-info">Draft Tasks</h4>
                    </Col>
                </Row>
            )
        },
        "in_progress": {
            title: "In Progress",
            content: (
                <Row className="p-2">
                    <Col sm="12" className="p-2">
                        <h4 className="text-primary">In Progress Tasks</h4>
                    </Col>
                </Row>
            )
        },
        "completed": {
            title: "Completed",
            content: (
                <Row className="p-2">
                    <Col sm="12" className="p-2">
                        <h4 className="text-success">Completed Tasks</h4>
                    </Col>
                </Row>
            )
        }
    }

    const [activeTab, setActiveTab] = useState('in_progress');
    
    const toggle = tab => {
        if (activeTab !== tab) setActiveTab(tab);
    }

    return (
        <div className="row p-4">
            <div className="col-lg-12">
                <h2 className="mb-4">Tasks</h2>

                <Nav tabs>
                    {
                        Object.entries(tabs).map((tab) => (
                            <NavItem key={tab[0]}>
                                <NavLink
                                    className={activeTab === tab[0] ? "active" : ""}
                                    onClick={() => {
                                        toggle(tab[0]);
                                    }}
                                    role="button"
                                >
                                    {tab[1].title}
                                </NavLink>
                            </NavItem>
                        ))
                    }
                </Nav>

                <TabContent activeTab={activeTab}>
                    {
                        Object.entries(tabs).map((tab) => (
                            <TabPane key={tab[0]} tabId={tab[0]}>
                                {tab[1].content}
                            </TabPane>
                        ))
                    }
                </TabContent>
            </div>
        </div>
    );
}

export default App;
jsx

Install React Router Dom

1
npm install --save react-router-dom
bash

React Router is a routing library for React. React Router Dom is the dom binding package for React Router.

Create a Route with an Optional Parameter Using React Router Dom

Instead of just loading the App component directly in the index.js, define a route for the App component.

Change the index.js to this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {BrowserRouter, Switch, Route} from "react-router-dom";

ReactDOM.render(
  <React.StrictMode>
      <BrowserRouter>
          <Switch>
              <Route path="/:active_tab?" component={App}/>
          </Switch>
      </BrowserRouter>
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
jsx

/:active_tab? means that whenever the route on the browser is / then the component to be loaded is App. The route can also take a parameter, though this is not mandatory. For example:

  • / - the active_tab parameter will be undefined
  • /in_progress - the active_tab parameter will be in_progress

So let's take advantage of this and ensure we can maintain our active tabs even on refreshing the page.

Refactor the Tabs to Use the URL active_page parameter

Start by creating a variable to hold the default active tab.

1
2
3
```jsx
const DEFAULT_ACTIVE_TAB = "in_progress";
```

The URL Parameter can be accessed using React Router Dom's useParams hook:

1
2
3
```jsx
const {active_tab} = useParams();
```

Do away with activeTab state variable. Initialize history variable with React Router Dom's useHistory hook, which is used for navigation.

1
2
3
```jsx
const history = useHistory();
```

Default the active_tab parameter to the set DEFAULT_ACTIVE_TAB if not active_tab is specified on the URL.

1
2
3
4
5
6
7
```jsx
useEffect(() => {
    if(!active_tab){
        history.push(`/${DEFAULT_ACTIVE_TAB}`);
    }
}, []);
```

Refactor the toggle function to the route to the specified tab on click of a NavLink, thereby updating the active_tab URL parameter.

1
2
3
4
5
6
7
```jsx
const toggle = tab => {
 if (active_tab !== tab) {
      history.push(`/${tab}`);
 }
}
```

Refactor all initial instances of activeTab to active_tab. The final code should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
```jsx
import React, {useEffect} from 'react';
import {Col, Nav, NavItem, NavLink, Row, TabContent, TabPane} from 'reactstrap';
import {useParams, useHistory} from "react-router-dom"

function App() {
    const DEFAULT_ACTIVE_TAB = "in_progress";
    const tabs = {
        "draft": {
            title: "Draft",
            content: (
                <Row className="p-2">
                    <Col sm="12" className="p-2">
                        <h4 className="text-info">Draft Tasks</h4>
                    </Col>
                </Row>
            )
        },
        "in_progress": {
            title: "In Progress",
            content: (
                <Row className="p-2">
                    <Col sm="12" className="p-2">
                        <h4 className="text-primary">In Progress Tasks</h4>
                    </Col>
                </Row>
            )
        },
        "completed": {
            title: "Completed",
            content: (
                <Row className="p-2">
                    <Col sm="12" className="p-2">
                        <h4 className="text-success">Completed Tasks</h4>
                    </Col>
                </Row>
            )
        }
    }

    const {active_tab} = useParams();
    const history = useHistory();
    
    useEffect(() => {
        if(!active_tab){
            history.push(`/${DEFAULT_ACTIVE_TAB}`);
        }
    }, []);

    const toggle = tab => {
       if (active_tab !== tab) {
            history.push(`/${tab}`);
       }
    }

    return (
        <div className="row p-4">
            <div className="col-lg-12">
                <h2 className="mb-4">Tasks</h2>

                <Nav tabs>
                    {
                        Object.entries(tabs).map((tab) => (
                            <NavItem key={tab[0]}>
                                <NavLink
                                    className={active_tab === tab[0] ? "active" : ""}
                                    onClick={() => {
                                        toggle(tab[0]);
                                    }}
                                    role="button"
                                >
                                    {tab[1].title}
                                </NavLink>
                            </NavItem>
                        ))
                    }
                </Nav>

                <TabContent activeTab={active_tab}>
                    {
                        Object.entries(tabs).map((tab) => (
                            <TabPane key={tab[0]} tabId={tab[0]}>
                                {tab[1].content}
                            </TabPane>
                        ))
                    }
                </TabContent>
            </div>
        </div>
    );
}

export default App;
```

Conclusion

There you have it. The active tab is now maintained using the active_tab URL Parameter. You can now share a link to access a specific tab, e.g., /completed, to redirect to the page with the completed tab active.

You are now familiar with some of the benefits of optional parameters on routes using React Router Dom and how to implement it. The next challenge is to explore other instances where this could be useful in your React apps.

2