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

Commit bfa9019

Browse files
authored
Merge pull request #154 from geotrev/app/hook-components
Convert class components to functional
2 parents f0e45b3 + 5cb9ccc commit bfa9019

File tree

19 files changed

+253
-433
lines changed

19 files changed

+253
-433
lines changed

app/components/Article/Article.js

Lines changed: 27 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,40 @@
1-
import React from "react"
1+
import React, { useState, useEffect } from "react"
22
import Markdown from "react-markdown"
33
import Prism from "prismjs"
4-
import Undernet from "undernet"
54
import classNames from "classnames"
65
import PropTypes from "prop-types"
76

7+
import Undernet from "undernet"
8+
import { COMPONENTS } from "./constants"
89
import ScrollUpOnMount from "app/components/ScrollUpOnMount"
910

10-
export default class Article extends React.Component {
11-
constructor() {
12-
super()
13-
14-
this.state = {
15-
domIsLoaded: false,
16-
}
17-
}
18-
19-
COMPONENTS = ["Tooltips", "Accordions", "Modals", "Dropdowns"]
11+
export default function Article(props) {
12+
const [domIsLoaded, setDomIsLoaded] = useState(false)
2013

21-
static propTypes = {
22-
children: PropTypes.any,
14+
const componentUnmountFunction = () => {
15+
COMPONENTS.forEach(component => Undernet[component].stop())
2316
}
2417

25-
componentDidMount() {
18+
useEffect(() => {
2619
Prism.highlightAll()
20+
COMPONENTS.forEach(component => Undernet[component].start())
21+
setDomIsLoaded(true)
22+
23+
return componentUnmountFunction
24+
}, [])
25+
26+
return (
27+
<article
28+
className={classNames("article-wrapper has-no-padding column", {
29+
fadeIn: domIsLoaded,
30+
})}
31+
>
32+
<ScrollUpOnMount />
33+
<Markdown source={props.children} escapeHtml={false} />
34+
</article>
35+
)
36+
}
2737

28-
// initialize all Undernet components
29-
// DO NOT init focus outline - it is set up in layouts/Main
30-
this.COMPONENTS.forEach(component => Undernet[component].start())
31-
32-
this.setState({ domIsLoaded: true })
33-
}
34-
35-
componentWillUnmount() {
36-
this.COMPONENTS.forEach(component => Undernet[component].stop())
37-
}
38-
39-
render() {
40-
return (
41-
<article
42-
className={classNames("article-wrapper has-no-padding column", {
43-
fadeIn: this.state.domIsLoaded,
44-
})}
45-
>
46-
<ScrollUpOnMount />
47-
<Markdown source={this.props.children} escapeHtml={false} />
48-
</article>
49-
)
50-
}
38+
Article.propTypes = {
39+
children: PropTypes.any,
5140
}

app/components/Article/__tests__/Article.spec.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@ import React from "react"
22
import Article from "../Article"
33
import Undernet from "undernet"
44
import Prism from "prismjs"
5+
import { COMPONENTS } from "../constants"
56

67
jest.mock("app/components/ScrollUpOnMount", () => global.simpleMock("ScrollUpOnMount"))
78

89
global.scrollTo = jest.fn()
910

10-
const components = ["Tooltips", "Accordions", "Modals", "Dropdowns"]
11-
12-
components.forEach(component => {
11+
COMPONENTS.forEach(component => {
1312
Undernet[component].start = jest.fn()
1413
Undernet[component].stop = jest.fn()
1514
})
@@ -52,7 +51,7 @@ describe("<Article />", () => {
5251
expect(Prism.highlightAll).toHaveBeenCalled()
5352
})
5453

55-
components.forEach(component => {
54+
COMPONENTS.forEach(component => {
5655
it(`calls Undernet.${component}.start`, () => {
5756
// Given
5857
mountComponent()
@@ -63,7 +62,7 @@ describe("<Article />", () => {
6362
})
6463

6564
describe("#componentWillUnmount", () => {
66-
components.forEach(component => {
65+
COMPONENTS.forEach(component => {
6766
it(`calls Undernet.${component}.stop`, () => {
6867
// Given
6968
const wrapper = mountComponent()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const COMPONENTS = ["Tooltips", "Accordions", "Modals", "Dropdowns"]
Lines changed: 26 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,37 @@
1-
import React, { createRef } from "react"
1+
import React, { createRef, useState, useEffect } from "react"
22
import { withLastLocation } from "react-router-last-location"
33
import PropTypes from "prop-types"
44

55
import { FOCUSABLE_TABINDEX, UNFOCUSABLE_TABINDEX } from "./constants"
66

7-
class PageHeader extends React.Component {
8-
constructor(props) {
9-
super(props)
10-
11-
this.state = { tabIndex: UNFOCUSABLE_TABINDEX }
12-
this.headerRef = createRef()
13-
}
14-
15-
static propTypes = {
16-
children: PropTypes.any.isRequired,
17-
className: PropTypes.string,
18-
lastLocation: PropTypes.object,
19-
}
20-
21-
componentDidMount() {
22-
if (!this.props.lastLocation) return
23-
24-
this.setState({ tabIndex: FOCUSABLE_TABINDEX }, () => {
25-
this.headerRef.current.focus()
26-
})
27-
}
28-
29-
handleBlur = () => {
30-
if (this.state.tabIndex === UNFOCUSABLE_TABINDEX) return
31-
this.setState({ tabIndex: UNFOCUSABLE_TABINDEX })
7+
const PageHeader = props => {
8+
const [tabIndex, setTabIndex] = useState(FOCUSABLE_TABINDEX)
9+
const headerRef = createRef()
10+
11+
useEffect(() => {
12+
if (props.lastLocation) {
13+
headerRef.current.focus()
14+
} else {
15+
setTabIndex(UNFOCUSABLE_TABINDEX)
16+
}
17+
}, [])
18+
19+
const handleBlur = () => {
20+
if (tabIndex === UNFOCUSABLE_TABINDEX) return
21+
setTabIndex(UNFOCUSABLE_TABINDEX)
3222
}
3323

34-
render() {
35-
const { className, children } = this.props
24+
return (
25+
<h1 onBlur={handleBlur} tabIndex={tabIndex} ref={headerRef} className={props.className}>
26+
{props.children}
27+
</h1>
28+
)
29+
}
3630

37-
return (
38-
<h1
39-
onBlur={this.handleBlur}
40-
tabIndex={this.state.tabIndex}
41-
ref={this.headerRef}
42-
className={className}
43-
>
44-
{children}
45-
</h1>
46-
)
47-
}
31+
PageHeader.propTypes = {
32+
children: PropTypes.any.isRequired,
33+
className: PropTypes.string,
34+
lastLocation: PropTypes.object,
4835
}
4936

5037
export default withLastLocation(PageHeader)
Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import React from "react"
1+
import { useEffect } from "react"
22

3-
export default class ScrollUpOnMount extends React.Component {
4-
componentDidMount() {
3+
export default function ScrollUpOnMount() {
4+
useEffect(() => {
55
window.scrollTo(0, 0)
6-
}
6+
}, [])
77

8-
render() {
9-
return null
10-
}
8+
return null
119
}

app/components/ScrollUpOnMount/__tests__/ScrollUpOnMount.spec.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
11
import React from "react"
22
import ScrollUpOnMount from "../ScrollUpOnMount"
33

4-
global.scrollTo = jest.fn()
5-
64
describe("<ScrollUpOnMount />", () => {
7-
it("renders", () => {
8-
const wrapper = mount(<ScrollUpOnMount />)
9-
expect(wrapper).toMatchSnapshot()
10-
})
11-
125
it("calls window.scrollTo when mounted", () => {
136
global.scrollTo = jest.fn()
147
mount(<ScrollUpOnMount />)

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

Lines changed: 0 additions & 3 deletions
This file was deleted.

app/components/SetMeta/SetMeta.js

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,31 @@ import React from "react"
22
import PropTypes from "prop-types"
33
import { Helmet } from "react-helmet"
44

5-
export default class SetMeta extends React.Component {
6-
static propTypes = {
7-
title: PropTypes.string,
8-
description: PropTypes.string,
9-
pageNotFound: PropTypes.bool,
10-
}
5+
import { titleTemplate } from "./constants"
116

12-
static defaultProps = {
13-
title: "Page Not Found",
14-
description: "",
15-
pageNotFound: false,
16-
}
7+
export default function SetMeta(props) {
8+
return (
9+
<Helmet titleTemplate={titleTemplate("%s")}>
10+
<title itemProp="name" lang="en">
11+
{props.title}
12+
</title>
1713

18-
render() {
19-
return (
20-
<Helmet titleTemplate="Undernet – %s">
21-
<title itemProp="name" lang="en">
22-
{this.props.title}
23-
</title>
14+
<meta name="description" content={props.description} />
2415

25-
<meta name="description" content={this.props.description} />
26-
<link rel="canonical" href={window.location.href} />
16+
{!props.pageNotFound && <link rel="canonical" href={window.location.href} />}
17+
{props.pageNotFound && <meta name="prerender-status-code" content="404" />}
18+
</Helmet>
19+
)
20+
}
21+
22+
SetMeta.propTypes = {
23+
title: PropTypes.string,
24+
description: PropTypes.string,
25+
pageNotFound: PropTypes.bool,
26+
}
2727

28-
{this.props.pageNotFound && <meta name="prerender-status-code" content="404" />}
29-
</Helmet>
30-
)
31-
}
28+
SetMeta.defaultProps = {
29+
title: "Page Not Found",
30+
description: "",
31+
pageNotFound: false,
3232
}
Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,42 @@
11
import React from "react"
2+
import Helmet from "react-helmet"
23
import SetMeta from "../SetMeta"
4+
import { titleTemplate } from "../constants"
5+
import jestConfig from "projectRoot/jest.config"
36

47
describe("<SetMeta />", () => {
5-
it("renders", () => {
6-
const wrapper = mount(<SetMeta title="Test title" description="Test description" />)
7-
expect(wrapper).toMatchSnapshot()
8-
})
8+
describe("Helmet", () => {
9+
let wrapper
10+
11+
afterEach(() => {
12+
wrapper.unmount()
13+
})
14+
15+
it("creates correct meta, link, and title data", () => {
16+
// Given
17+
const meta = [{ name: "description", content: "Test description" }]
18+
const title = "Test title"
19+
const links = [{ rel: "canonical", href: jestConfig.testURL }]
20+
wrapper = mount(<SetMeta title={title} description={meta[0].content} />)
21+
// Then
22+
expect(Helmet.peek().metaTags).toEqual(meta)
23+
expect(Helmet.peek().linkTags).toEqual(links)
24+
expect(Helmet.peek().title).toEqual(titleTemplate(title))
25+
})
926

10-
it("renders with pageNotFound state", () => {
11-
const wrapper = mount(<SetMeta pageNotFound={true} />)
12-
expect(wrapper).toMatchSnapshot()
27+
it("creates correct 404 meta and title data", () => {
28+
// Given
29+
const meta = [
30+
{ name: "description", content: "" },
31+
{ name: "prerender-status-code", content: "404" },
32+
]
33+
const title = "Page Not Found"
34+
const links = []
35+
wrapper = mount(<SetMeta pageNotFound={true} />)
36+
// Then
37+
expect(Helmet.peek().metaTags).toEqual(meta)
38+
expect(Helmet.peek().linkTags).toEqual(links)
39+
expect(Helmet.peek().title).toEqual(titleTemplate(title))
40+
})
1341
})
1442
})

0 commit comments

Comments
 (0)