Skip to content
This repository was archived by the owner on Oct 1, 2024. It is now read-only.

Commit f0e45b3

Browse files
authored
Merge pull request #153 from geotrev/app/hooks
Update SideNav to use react hooks
2 parents e37113e + 180e493 commit f0e45b3

File tree

6 files changed

+88
-241
lines changed

6 files changed

+88
-241
lines changed

app/components/SideNav/SideNav.js

Lines changed: 71 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,77 @@
1-
import React, { Fragment } from "react"
2-
import PropTypes from "prop-types"
1+
import React, { useState, useEffect } from "react"
32
import classNames from "classnames"
43
import throttle from "lodash/throttle"
54
import { NavLink } from "react-router-dom"
65
import Menu from "react-feather/dist/icons/menu"
76
import ChevronRight from "react-feather/dist/icons/chevron-right"
8-
import { Accordions } from "undernet"
97

8+
import { NAV_DATA } from "app/components/SideNav/navData"
9+
import { Accordions } from "undernet"
1010
import "./styles.scss"
1111

1212
const pkg = require("projectRoot/package.json")
1313
const MENU_COLLAPSE_WIDTH = 1199
1414

15-
export default class SideNav extends React.Component {
16-
constructor(props) {
17-
super(props)
18-
this.handleMenuVisibility = throttle(this.handleMenuVisibility, 50)
15+
export default function SideNav() {
16+
// set up effects and state
1917

20-
this.state = {
21-
menuIsOpen: window.innerWidth > MENU_COLLAPSE_WIDTH,
22-
}
18+
const getWindowInnerWidth = () => {
19+
return window.innerWidth
2320
}
2421

25-
static propTypes = {
26-
navItems: PropTypes.arrayOf(
27-
PropTypes.shape({
28-
header: PropTypes.string,
29-
links: PropTypes.arrayOf(
30-
PropTypes.shape({
31-
name: PropTypes.string,
32-
url: PropTypes.string,
33-
})
34-
),
35-
})
36-
).isRequired,
37-
navListClasses: PropTypes.string,
38-
}
22+
const isLargerThanCollapseWidth = getWindowInnerWidth() > MENU_COLLAPSE_WIDTH
23+
const [menuIsOpen, setMenuIsOpen] = useState(isLargerThanCollapseWidth)
3924

40-
componentDidMount() {
41-
window.addEventListener("resize", this.handleMenuVisibility)
25+
let handleMenuVisibility = () => {
26+
if (isLargerThanCollapseWidth) {
27+
setMenuIsOpen(true)
28+
}
29+
}
4230

43-
const menuIsOpen = window.innerWidth > MENU_COLLAPSE_WIDTH
44-
this.setState({ menuIsOpen })
31+
handleMenuVisibility = throttle(handleMenuVisibility, 50)
4532

33+
useEffect(() => {
4634
Accordions.start()
47-
}
48-
49-
componentWillUnmount() {
50-
window.removeEventListener("resize", this.handleMenuVisibility)
51-
Accordions.stop()
52-
}
35+
window.addEventListener("resize", handleMenuVisibility)
36+
setMenuIsOpen(isLargerThanCollapseWidth)
5337

54-
componentDidUpdate(_, prevState) {
55-
if (prevState.menuIsOpen !== this.state.menuIsOpen) {
38+
return () => {
39+
window.removeEventListener("resize", handleMenuVisibility)
5640
Accordions.stop()
57-
Accordions.start()
5841
}
59-
}
42+
}, [])
6043

61-
handleCollapseClick = () => {
62-
if (window.innerWidth <= MENU_COLLAPSE_WIDTH) {
63-
this.setState({ menuIsOpen: false })
64-
}
65-
}
44+
useEffect(() => {
45+
Accordions.stop()
46+
Accordions.start()
47+
}, [menuIsOpen])
6648

67-
handleMenuVisibility = () => {
68-
if (window.innerWidth > MENU_COLLAPSE_WIDTH) {
69-
this.setState({ menuIsOpen: true })
49+
// set up handlers
50+
51+
const handleCollapseClick = () => {
52+
if (getWindowInnerWidth() <= MENU_COLLAPSE_WIDTH) {
53+
setMenuIsOpen(false)
7054
}
7155
}
7256

73-
handleMenuToggleClick = event => {
57+
const handleMenuToggleClick = event => {
7458
event.preventDefault()
75-
this.setState({ menuIsOpen: !this.state.menuIsOpen })
59+
setMenuIsOpen(!menuIsOpen)
7660
}
7761

78-
get buttonClasses() {
62+
const buttonClasses = () => {
7963
return classNames("is-justified-center is-aligned-center is-flex is-hidden-xlarge", {
80-
"rotate-180": !this.state.menuIsOpen,
64+
"rotate-180": !menuIsOpen,
8165
})
8266
}
8367

84-
get menuClasses() {
68+
const menuClasses = () => {
8569
return classNames("row side-nav-menu accordion has-padding-3", {
86-
"is-hidden": !this.state.menuIsOpen,
70+
"is-hidden": !menuIsOpen,
8771
})
8872
}
8973

90-
accordionIsActive(items) {
74+
const accordionIsActive = items => {
9175
let isActive = false
9276

9377
items.forEach(item => {
@@ -99,14 +83,16 @@ export default class SideNav extends React.Component {
9983
return isActive
10084
}
10185

102-
renderAccordionChildLink = item => {
86+
// render content
87+
88+
const renderAccordionChildLink = item => {
10389
return (
10490
<li key={item.name} role="none">
10591
<NavLink
10692
role="listitem"
10793
className="side-nav-link-item has-black-text is-flex is-aligned-center"
10894
activeClassName="active"
109-
onClick={this.handleCollapseClick}
95+
onClick={handleCollapseClick}
11096
to={item.url}
11197
>
11298
{item.name} <ChevronRight size={16} role="presentation" focusable="false" />
@@ -115,11 +101,11 @@ export default class SideNav extends React.Component {
115101
)
116102
}
117103

118-
renderAccordionRow(section, index) {
119-
const listItems = section.links.map(this.renderAccordionChildLink)
104+
const renderAccordionRow = (section, index) => {
105+
const listItems = section.links.map(renderAccordionChildLink)
120106

121107
return (
122-
<Fragment key={section.header}>
108+
<React.Fragment key={section.header}>
123109
<h4 className="paragraph">
124110
<button
125111
id={`nav-acc-button${index}`}
@@ -133,53 +119,47 @@ export default class SideNav extends React.Component {
133119
<ul className="accordion-content" id={`nav-acc-content${index}`}>
134120
{listItems}
135121
</ul>
136-
</Fragment>
122+
</React.Fragment>
137123
)
138124
}
139125

140-
renderNavAccordion() {
141-
return this.props.navItems.map((section, i) => {
126+
const renderNavAccordion = () => {
127+
return NAV_DATA.map((section, i) => {
142128
return (
143129
<div
144-
data-visible={this.accordionIsActive(section.links) ? "true" : "false"}
130+
data-visible={accordionIsActive(section.links) ? "true" : "false"}
145131
data-accordion-row={`nav-acc-content${i}`}
146-
className={classNames("accordion-row", this.props.navListClasses)}
132+
className="accordion-row xsmall-12 columns has-no-padding"
147133
key={section.links[0].url}
148134
>
149-
{this.renderAccordionRow(section, i)}
135+
{renderAccordionRow(section, i)}
150136
</div>
151137
)
152138
})
153139
}
154140

155-
render() {
156-
return (
157-
<div className="xsmall-12 xlarge-2 columns has-no-padding" id="side-nav">
158-
<div className="fluid grid side-nav-wrapper">
159-
<div className="row is-flex is-hidden-xlarge side-nav-expand">
160-
<button
161-
onClick={this.handleMenuToggleClick}
162-
className={this.buttonClasses}
163-
aria-controls="side-nav-wrapper"
164-
aria-expanded={this.state.menuIsOpen}
165-
>
166-
<Menu size={20} role="presentation" focusable="false" />{" "}
167-
<span className="has-black-text">Explore</span>
168-
</button>
169-
</div>
170-
171-
<nav
172-
data-accordion="side-nav-accordion"
173-
className={this.menuClasses}
174-
id="side-nav-wrapper"
141+
return (
142+
<div className="xsmall-12 xlarge-2 columns has-no-padding" id="side-nav">
143+
<div className="fluid grid side-nav-wrapper">
144+
<div className="row is-flex is-hidden-xlarge side-nav-expand">
145+
<button
146+
onClick={handleMenuToggleClick}
147+
className={buttonClasses()}
148+
aria-controls="side-nav-wrapper"
149+
aria-expanded={menuIsOpen}
175150
>
176-
<p className="version-text has-no-padding has-gray800-text xsmall-12 columns">
177-
Version {pkg.version}
178-
</p>
179-
{this.renderNavAccordion()}
180-
</nav>
151+
<Menu size={20} role="presentation" focusable="false" />{" "}
152+
<span className="has-black-text">Explore</span>
153+
</button>
181154
</div>
155+
156+
<nav data-accordion="side-nav-accordion" className={menuClasses()} id="side-nav-wrapper">
157+
<p className="version-text has-no-padding has-gray800-text xsmall-12 columns">
158+
Version {pkg.version}
159+
</p>
160+
{renderNavAccordion()}
161+
</nav>
182162
</div>
183-
)
184-
}
163+
</div>
164+
)
185165
}

app/components/SideNav/__tests__/SideNav.spec.js

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@ import { Accordions } from "undernet"
55

66
jest.mock("react-feather/dist/icons/chevron-right", () => global.simpleMock("ChevronRight"))
77
jest.mock("react-feather/dist/icons/menu", () => global.simpleMock("Menu"))
8-
jest.mock("projectRoot/package.json", () => ({
9-
version: "9.9.9",
8+
jest.mock("projectRoot/package.json", () => ({ version: "9.9.9" }))
9+
jest.mock("app/components/SideNav/navData", () => ({
10+
NAV_DATA: [
11+
{
12+
header: "Test Header",
13+
links: [{ name: "Test 1", url: "#" }],
14+
},
15+
],
1016
}))
1117

1218
Accordions.start = jest.fn()
@@ -15,17 +21,11 @@ Accordions.stop = jest.fn()
1521
const MENU_COLLAPSE_WIDTH = 1199
1622
const MENU_EXPAND_WIDTH = MENU_COLLAPSE_WIDTH + 1
1723
const DEFAULT_WIDTH = 1024
18-
const navItems = [
19-
{
20-
header: "Test Header",
21-
links: [{ name: "Test 1", url: "#" }],
22-
},
23-
]
2424

2525
function mountComponent() {
2626
return mount(
2727
<Router>
28-
<SideNav navItems={navItems} />
28+
<SideNav />
2929
</Router>
3030
)
3131
}
@@ -35,7 +35,7 @@ function updatePageWidth(width) {
3535
window.dispatchEvent(new Event("resize"))
3636
}
3737

38-
describe.only("<SideNav />", () => {
38+
describe("<SideNav />", () => {
3939
describe("#render", () => {
4040
it("renders", () => {
4141
// Given
@@ -45,21 +45,6 @@ describe.only("<SideNav />", () => {
4545
})
4646
})
4747

48-
describe("#state", () => {
49-
beforeEach(() => {
50-
const wrapper = mountComponent()
51-
wrapper.find("SideNav").setState({ menuIsOpen: true })
52-
})
53-
54-
it("calls Accordions.stop if state.menuIsOpen changes", () => {
55-
expect(Accordions.stop).toHaveBeenCalled()
56-
})
57-
58-
it("calls Accordions.start if state.menuIsOpen changes", () => {
59-
expect(Accordions.start).toHaveBeenCalled()
60-
})
61-
})
62-
6348
describe("#handleMenuVisibility", () => {
6449
afterEach(() => {
6550
updatePageWidth(DEFAULT_WIDTH)

app/components/SideNav/__tests__/__snapshots__/SideNav.spec.js.snap

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ exports[`<SideNav /> #handleMenuToggleClick toggles menu when button is clicked
3838
9.9.9
3939
</p>
4040
<div
41-
className="accordion-row"
41+
className="accordion-row xsmall-12 columns has-no-padding"
4242
data-accordion-row="nav-acc-content0"
4343
data-visible="false"
4444
key="#"
@@ -139,7 +139,7 @@ exports[`<SideNav /> #handleMenuVisibility hides menu at or below 1199 1`] = `
139139
9.9.9
140140
</p>
141141
<div
142-
className="accordion-row"
142+
className="accordion-row xsmall-12 columns has-no-padding"
143143
data-accordion-row="nav-acc-content0"
144144
data-visible="false"
145145
key="#"
@@ -240,7 +240,7 @@ exports[`<SideNav /> #handleMenuVisibility shows menu above 1199 1`] = `
240240
9.9.9
241241
</p>
242242
<div
243-
className="accordion-row"
243+
className="accordion-row xsmall-12 columns has-no-padding"
244244
data-accordion-row="nav-acc-content0"
245245
data-visible="false"
246246
key="#"
@@ -304,21 +304,7 @@ exports[`<SideNav /> #handleMenuVisibility shows menu above 1199 1`] = `
304304
`;
305305

306306
exports[`<SideNav /> #render renders 1`] = `
307-
<SideNav
308-
navItems={
309-
Array [
310-
Object {
311-
"header": "Test Header",
312-
"links": Array [
313-
Object {
314-
"name": "Test 1",
315-
"url": "#",
316-
},
317-
],
318-
},
319-
]
320-
}
321-
>
307+
<SideNav>
322308
<div
323309
className="xsmall-12 xlarge-2 columns has-no-padding"
324310
id="side-nav"
@@ -360,7 +346,7 @@ exports[`<SideNav /> #render renders 1`] = `
360346
9.9.9
361347
</p>
362348
<div
363-
className="accordion-row"
349+
className="accordion-row xsmall-12 columns has-no-padding"
364350
data-accordion-row="nav-acc-content0"
365351
data-visible="false"
366352
key="#"

app/pages/Docs/Docs.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@ import React from "react"
22

33
import SideNav from "app/components/SideNav"
44
import DocsRoutes from "app/components/DocsRoutes"
5-
import { NAV_DATA } from "app/pages/Docs/navData"
65
import "./styles.scss"
76

87
export default function Docs() {
98
return (
109
<div id="docs" className="fluid grid has-no-padding">
1110
<div className="row">
12-
<SideNav navListClasses="xsmall-12 columns has-no-padding" navItems={NAV_DATA} />
11+
<SideNav />
1312
<div className="xsmall-12 xlarge-10 columns has-padding-3">
1413
<DocsRoutes />
1514
</div>

0 commit comments

Comments
 (0)