Skip to content

Commit 37e4b43

Browse files
ollelauribostromOnur Laru
authored andcommitted
refactor: Use hooks api. (nodejs#118)
1 parent d30b29d commit 37e4b43

5 files changed

Lines changed: 161 additions & 167 deletions

File tree

src/components/navigation-item.tsx

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useRef, useEffect } from 'react';
22
import { Link } from 'gatsby';
33

44
type Props = {
@@ -7,39 +7,36 @@ type Props = {
77
slug: string;
88
title: string;
99
onClick: () => void;
10-
}
10+
};
1111

12-
class NavigationItem extends React.Component<Props> {
13-
private element?: HTMLAnchorElement;
14-
15-
setReference = (ref: HTMLAnchorElement) => {
16-
if (this.props.isActive) {
17-
this.element = ref;
12+
const NavigationItem = ({ isDone, isActive, slug, title, onClick }: Props) => {
13+
const element = useRef<HTMLAnchorElement | null>(null);
14+
15+
const handleRef = (ref?: HTMLAnchorElement | null) => {
16+
if (ref && isActive) {
17+
element.current = ref;
1818
}
1919
}
2020

21-
componentDidMount() {
22-
if (this.element) {
21+
useEffect(() => {
22+
if (element.current) {
2323
// TODO: Scroll ref element in to view
24-
// this.element.scrollIntoView(true);
24+
// maybe use utils/scrollTo
2525
}
26-
}
26+
});
2727

28-
render() {
29-
const { isDone, isActive, slug, title, onClick } = this.props;
30-
let className = 'side-nav__item ';
31-
if (isDone) {
32-
className += 'side-nav__item--done';
33-
} else if (isActive) {
34-
className += 'side-nav__item--active';
35-
}
36-
37-
return (
38-
<Link to={`/learn/${slug}`} ref={this.setReference} onClick={onClick} className={className}>
39-
{title}
40-
</Link>
41-
)
28+
let className = 'side-nav__item ';
29+
if (isDone) {
30+
className += 'side-nav__item--done';
31+
} else if (isActive) {
32+
className += 'side-nav__item--active';
4233
}
43-
}
4434

45-
export default NavigationItem
35+
return (
36+
<Link ref={handleRef} to={`/learn/${slug}`} onClick={onClick} className={className}>
37+
{title}
38+
</Link>
39+
);
40+
};
41+
42+
export default NavigationItem;

src/components/navigation.tsx

Lines changed: 31 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,35 @@
1-
import React from 'react';
1+
import React, { useState } from 'react';
22
import NavigationSection from './navigation-section';
3-
import { RemarkPage, NavigationSectionData } from '../types';
4-
5-
/** Small screen width
6-
* If the width of the viewport is lesser than this value
7-
* it means that the website is viewed in a tablet or mobile
8-
* TODO have this number shared in one place in the project
9-
*/
10-
const MAX_SMALLSCREEN_WIDTH = 1262
3+
import { NavigationSectionData } from '../types';
4+
import { isSmallScreen } from '../util/isSmallScreen';
115

126
type Props = {
13-
activePage: RemarkPage;
147
sections: NavigationSectionData[];
15-
}
16-
17-
type State = {
18-
isOpen: boolean;
19-
}
20-
21-
class Navigation extends React.Component<Props, State> {
22-
state = {
23-
isOpen: false
24-
}
25-
26-
toggle = () => {
27-
this.setState({ isOpen: !this.state.isOpen });
28-
}
29-
30-
onItemClick = () => {
31-
// Get viewport width
32-
// Source - https://stackoverflow.com/a/8876069/2621400
33-
const w = Math.max(
34-
document.documentElement.clientWidth,
35-
window.innerWidth || 0
36-
)
37-
// If width is lesser or equal to max small screen width
38-
if (w <= MAX_SMALLSCREEN_WIDTH) {
39-
this.toggle();
40-
}
41-
}
42-
43-
render() {
44-
const className = this.state.isOpen ? 'side-nav side-nav--open' : 'side-nav';
45-
46-
return (
47-
<nav className={className}>
48-
<button className="side-nav__open" onClick={this.toggle}>Menu</button>
49-
{this.props.sections.map((section: NavigationSectionData) => {
50-
return (
51-
<NavigationSection
52-
title={section.title}
53-
items={section.items}
54-
key={section.title}
55-
onItemClick={this.onItemClick}
56-
/>
57-
)
58-
})}
59-
</nav>
60-
)
61-
}
62-
}
63-
64-
export default Navigation
8+
};
9+
10+
const Navigation = ({ sections }: Props) => {
11+
const [isOpen, setIsOpen] = useState(false);
12+
const toggle = () => setIsOpen(!isOpen);
13+
const onItemClick = () => isSmallScreen() && toggle();
14+
const className = isOpen ? 'side-nav side-nav--open' : 'side-nav';
15+
16+
return (
17+
<nav className={className}>
18+
<button className="side-nav__open" onClick={toggle}>
19+
Menu
20+
</button>
21+
{sections.map((section: NavigationSectionData) => {
22+
return (
23+
<NavigationSection
24+
title={section.title}
25+
items={section.items}
26+
key={section.title}
27+
onItemClick={onItemClick}
28+
/>
29+
);
30+
})}
31+
</nav>
32+
);
33+
};
34+
35+
export default Navigation;

src/pages/learn.tsx

Lines changed: 75 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useRef, useEffect } from 'react';
22
import { graphql } from 'gatsby';
33
import { LearnPageData } from '../types';
44
import Layout from '../components/layout';
@@ -11,99 +11,95 @@ import Page404 from './404';
1111
type Props = {
1212
data: LearnPageData;
1313
location: Location;
14-
}
14+
};
1515

16-
export default class LearnPage extends React.Component<Props> {
17-
prevOffset: number = -1;
18-
19-
componentDidMount() {
20-
this.magicHeroNumber();
21-
}
16+
export default ({ data, location }: Props) => {
17+
const prevOffset = useRef(-1);
2218

23-
/**
24-
* When on the "Learn" page, we need to update the background gradient
25-
* that runs the side menu's title color change from white to black
26-
* as it becomes sticky and overlays the hero banner.
27-
*/
28-
magicHeroNumber = () => {
29-
if (typeof window === 'undefined') { return; } // Guard for SSR.
19+
const magicHeroNumber = () => {
20+
if (typeof window === 'undefined') {
21+
return;
22+
} // Guard for SSR.
3023
const doc = window.document;
3124
const offset = Math.min(doc.scrollingElement!.scrollTop - 62, 210);
32-
if (Math.abs(this.prevOffset - offset) > 5) {
33-
this.prevOffset = offset;
25+
if (Math.abs(prevOffset.current - offset) > 5) {
26+
prevOffset.current = offset;
3427
doc.body.setAttribute('style', `--magic-hero-number: ${356 - offset}px`);
3528
}
36-
window.requestAnimationFrame(this.magicHeroNumber);
37-
}
29+
window.requestAnimationFrame(magicHeroNumber);
30+
};
3831

39-
render() {
40-
const { data, location } = this.props;
41-
const currentPage = location.pathname.split('/').pop();
42-
const { activePage, previousPage, nextPage, navigationSections } = findActive(data.sections.group, currentPage);
43-
if (!activePage) {
44-
// Rendering 404 page as a component here
45-
// The reason is to show the 404 component but maintaining the url (instead of redirecting to 404)
46-
return <Page404 />
47-
}
32+
useEffect(magicHeroNumber);
4833

49-
return (
50-
<Layout
51-
title={`${activePage.frontmatter.title} by ${activePage.frontmatter.author}`}
52-
description={activePage.frontmatter.description}
53-
>
54-
<Hero title={activePage.frontmatter.title} />
55-
<Navigation activePage={activePage} sections={navigationSections} />
56-
<Article page={activePage} previous={previousPage} next={nextPage} />
57-
</Layout>
58-
);
34+
const currentPage = location.pathname.split('/').pop();
35+
const { activePage, previousPage, nextPage, navigationSections } = findActive(
36+
data.sections.group,
37+
currentPage
38+
);
39+
40+
if (!activePage) {
41+
// Rendering 404 page as a component here
42+
// The reason is to show the 404 component but maintaining the url (instead of redirecting to 404)
43+
return <Page404 />;
5944
}
60-
}
6145

62-
export const query = graphql`{
63-
sections: allMarkdownRemark(
64-
sort: {
65-
fields: [fileAbsolutePath]
66-
order: ASC
67-
}
68-
) {
69-
group(field: frontmatter___section) {
70-
fieldValue
71-
edges {
72-
node {
73-
id
74-
fileAbsolutePath
75-
html,
76-
parent {
77-
... on File {
78-
relativePath
46+
return (
47+
<Layout
48+
title={`${activePage.frontmatter.title} by ${
49+
activePage.frontmatter.author
50+
}`}
51+
description={activePage.frontmatter.description}>
52+
<Hero title={activePage.frontmatter.title} />
53+
<Navigation sections={navigationSections} />
54+
<Article page={activePage} previous={previousPage} next={nextPage} />
55+
</Layout>
56+
);
57+
};
58+
59+
export const query = graphql`
60+
{
61+
sections: allMarkdownRemark(
62+
sort: { fields: [fileAbsolutePath], order: ASC }
63+
) {
64+
group(field: frontmatter___section) {
65+
fieldValue
66+
edges {
67+
node {
68+
id
69+
fileAbsolutePath
70+
html
71+
parent {
72+
... on File {
73+
relativePath
74+
}
75+
}
76+
frontmatter {
77+
title
78+
description
79+
author
80+
}
81+
fields {
82+
slug
7983
}
8084
}
81-
frontmatter {
82-
title
83-
description
84-
author
85-
}
86-
fields {
87-
slug
88-
}
89-
}
90-
next {
91-
frontmatter {
92-
title
93-
}
94-
fields {
95-
slug
96-
}
97-
}
98-
previous {
99-
frontmatter {
100-
title
85+
next {
86+
frontmatter {
87+
title
88+
}
89+
fields {
90+
slug
91+
}
10192
}
102-
fields {
103-
slug
93+
previous {
94+
frontmatter {
95+
title
96+
}
97+
fields {
98+
slug
99+
}
104100
}
105101
}
106102
}
107103
}
108104
}
109-
}`;
105+
`;

src/util/isSmallScreen.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/** Small screen width
2+
* If the width of the viewport is lesser than this value
3+
* it means that the website is viewed in a tablet or mobile
4+
*/
5+
export const MAX_SMALLSCREEN_WIDTH = 1262;
6+
7+
export const isSmallScreen = () => {
8+
// Get viewport width
9+
// Source - https://stackoverflow.com/a/8876069/2621400
10+
const w = Math.max(
11+
document.documentElement.clientWidth,
12+
window.innerWidth || 0
13+
);
14+
15+
return w <= MAX_SMALLSCREEN_WIDTH;
16+
}

test/util/isSmallScreen.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { isSmallScreen } from '../../src/util/isSmallScreen';
2+
3+
describe('Tests for isSmallScreen', () => {
4+
it('returns true for small screens', () => {
5+
// @ts-ignore
6+
window.innerWidth = 1262;
7+
expect(isSmallScreen()).toEqual(true);
8+
});
9+
it('returns false for large screens', () => {
10+
// @ts-ignore
11+
window.innerWidth = 1263;
12+
expect(isSmallScreen()).toEqual(false);
13+
});
14+
});

0 commit comments

Comments
 (0)