Skip to content

Commit b464941

Browse files
committed
Merge remote-tracking branch 'origin/master' into complete-graphql-update
2 parents ae1251c + 6ea9b16 commit b464941

File tree

81 files changed

+2121
-521
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+2121
-521
lines changed

.circleci/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ aliases:
5656
- <<: *persist_cache
5757
- <<: *attach_to_bootstrap
5858
- run: yarn jest -w 1
59+
- run: GATSBY_DB_NODES=loki yarn jest -w 1
5960

6061
e2e-test-workflow: &e2e-test-workflow
6162
filters:

.eslintrc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"prettier/flowtype",
1010
"prettier/react"
1111
],
12-
"plugins": ["flowtype", "prettier", "react"],
12+
"plugins": ["flowtype", "prettier", "react", "filenames"],
1313
"parserOptions": {
1414
"ecmaVersion": 2016,
1515
"sourceType": "module",
@@ -41,6 +41,7 @@
4141
}
4242
],
4343
"consistent-return": ["error"],
44+
"filenames/match-regex": ["error", "^[a-z-\\d\\.]+$", true],
4445
"no-console": "off",
4546
"no-inner-declarations": "off",
4647
"prettier/prettier": "error",

.gitattributes

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,15 @@
11
* text=auto eol=lf
2+
3+
# These files are binary and should be left untouched
4+
*.png binary
5+
*.jpg binary
6+
*.jpeg binary
7+
*.gif binary
8+
*.ico binary
9+
*.mov binary
10+
*.mp4 binary
11+
*.mp3 binary
12+
*.ttf binary
13+
*.eot binary
14+
*.woff binary
15+
*.pdf binary

benchmarks/markdown/gatsby-node.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ exports.createPages = ({ actions: { createPage } }) => {
2626
path: `/path/${step}/`,
2727
component: require.resolve(`./src/templates/blank.js`),
2828
context: {
29-
id: step,
29+
id: step.toString(),
3030
},
3131
})
3232
}

docs/blog/2018-06-07-build-a-gatsby-blog-using-the-cosmic-js-source-plugin/index.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ import { Link } from "gatsby"
124124
import get from "lodash/get"
125125
import { Helmet } from "react-helmet"
126126

127-
import Bio from "../components/Bio"
127+
import Bio from "../components/bio"
128128
import { rhythm } from "../utils/typography"
129129

130130
class BlogIndex extends React.Component {
@@ -215,7 +215,7 @@ import { Helmet } from "react-helmet"
215215
import { Link } from "gatsby"
216216
import get from "lodash/get"
217217

218-
import Bio from "../components/Bio"
218+
import Bio from "../components/bio"
219219
import { rhythm, scale } from "../utils/typography"
220220
import { relative } from "path"
221221

@@ -427,7 +427,7 @@ Restart the Gatsby server, then visit the detail page by clicking on URLs displa
427427

428428
### Extra Stuff!
429429

430-
In addition to the code covered in this tutorial, we also implemented `src/components/Bio.js` to display author information & `src/layouts/index.js` to [create a generic layout](/tutorial/part-three/#our-first-layout-component) for the blog.
430+
In addition to the code covered in this tutorial, we also implemented `src/components/bio.js` to display author information & `src/layouts/index.js` to [create a generic layout](/tutorial/part-three/#our-first-layout-component) for the blog.
431431

432432
The source code for this tutorial is available [on GitHub](https://github.com/cosmicjs/gatsby-blog-cosmicjs). To see it live, clone the repository, and run (`cd gatsby-blog-cosmicjs && npm i && npm run develop`) or check out the [demo on Netlify](https://gatsby-blog-cosmicjs.netlify.com/).
433433

docs/blog/2019-02-05-hapticmedia-case-study/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: "Beyond Static: Hapticmedia Uses Gatsby to a Build Dynamic Web App"
2+
title: "Beyond Static: Hapticmedia Uses Gatsby to Build a Dynamic Web App"
33
date: 2019-02-05
44
author: Linda Watkins
55
tags:
71.5 KB
Loading
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
---
2+
title: "Pragmatic Lessons from Converting to React Hooks"
3+
date: 2019-02-07
4+
author: Daniel Lemay
5+
tags:
6+
- react
7+
- react hooks
8+
image: "./images/hooks-diff.jpg"
9+
showImageInArticle: true
10+
canonicalLink: "https://www.dslemay.com/blog/2019/02/06/pragmatic-lessons-from-converting-to-react-hooks"
11+
---
12+
13+
Last week I decided to install the React 16.8 alpha on a branch and experiment with React Hooks in preparation for their release on February 6, 2018. The site utilized a [render prop](https://reactjs.org/docs/render-props.html) based Slideshow component in several places as well as a handful of other class based components. Through this process, I was able to consolidate the application code and eliminate all class based components from the site's code base. The React team does not recommend refactoring your entire codebase to Hooks on their release. I did this primarily as a means to engage with the Hooks API in a relatively small codebase. You can find the code conversion to Hooks discussed in this post at the [related PR](https://github.com/dslemay/Portfolio-Site/pull/10).
14+
15+
## Converting to Hooks and lessons learned
16+
17+
The [Hooks documentation](https://reactjs.org/docs/hooks-intro.html) is well presented and an excellent resource to getting started with hooks. I previously championed the render props pattern for reusable logic and composability. However, the extra syntax often comes with tradeoffs in clarity. These concerns include: "wrapper hell" and more mental overhead to parse nested JSX structure within the component. Adding a second render prop component or ternary operator further compounds these concerns. These additional wrappers also display in the React devTools and can get out of hand. The possibility of cleaning up this syntax and providing clearer code is alluring.
18+
19+
```javascript
20+
export const PureRandomQuote = ({
21+
data: {
22+
contentfulSlideshow: { slides },
23+
},
24+
}) => (
25+
<Slideshow slides={slides}>
26+
{({ slideData: quote }) => (
27+
<div className={gridStyles} data-testid="random-quote">
28+
<QuoteCard>
29+
<div>{quote.quote}</div>
30+
<div className={attribution}>{`~${quote.attribution}`}</div>
31+
</QuoteCard>
32+
</div>
33+
)}
34+
</Slideshow>
35+
)
36+
```
37+
38+
Several components on the site used a Slideshow render prop component. This exposed functionality to play/pause, pass down the current index, go to the previous/next slide, or go to a specific slide in the array. This component accepted an array of slides as a prop and provided an object as the argument in the returned function. Children components used these fields for data rendering or functionality. This is a common approach to the render prop pattern.
39+
40+
This previous implementation had several downsides. The component tracked the timer's ID as an instance variable to clear the timeout. The slide updater function accepted a second argument, to determine if it should clear the stored timeout and create a new one. This resolves the issue of stacking timeouts. A further source of additional code was the need for a state updater function. This is a requirement when accessing the current state in the setState call to ensure that the [updates are applied correctly](https://reactjs.org/docs/react-component.html#setstate). This particular function was complex enough that it was pulled out into it's own side-loaded module. Migrating to a custom hook alleviated these concerns and others.
41+
42+
```javascript
43+
// Can result in state batching issues and is an anti-pattern
44+
const updateStateBad = () => this.setState({ count: this.state.count + 1 })
45+
46+
// Preferred way to reference current state in setState call
47+
const updateStateGood = () =>
48+
this.setState(state => ({ count: state.count + 1 }))
49+
```
50+
51+
The custom `useSlideshow` hook utilizes two different hooks to replace the functionality of the render props component: `useState` and `useEffect`. The current index and playing states are both set with their own calls to useState. The `useEffect` hook checks if the isPlaying state is true and then sets the timeout to advance the slide to the next index. It resets to the first slide after it reaches the last index. The hook clears the timeout when the current index or isPlaying state changes. The hook includes a function to update the the slide. The necessary state and functions are return in an object.
52+
53+
```javascript
54+
function useSlideshow(slides, { timerLength = 5000 } = {}) {
55+
const [isPlaying, setIsPlaying] = useState(true)
56+
const [currIndex, setCurrIndex] = useState(0)
57+
58+
useEffect(() => {
59+
if (isPlaying) {
60+
const timer = setTimeout(
61+
() => setCurrIndex((currIndex + 1) % slides.length),
62+
timerLength
63+
)
64+
65+
return () => clearTimeout(timer)
66+
}
67+
}, [currIndex, isPlaying])
68+
69+
const updateSlide = (direction: Direction) => {
70+
if (typeof direction === "number") {
71+
return setCurrIndex(direction)
72+
}
73+
74+
if (direction === "next") {
75+
return setCurrIndex((currIndex + 1) % slides.length)
76+
}
77+
78+
return setCurrIndex((currIndex - 1 + slides.length) % slides.length)
79+
}
80+
81+
return {
82+
currIndex,
83+
isPlaying,
84+
setIsPlaying,
85+
updateSlide,
86+
}
87+
}
88+
```
89+
90+
The benefits of this structural change go beyond aesthetic cleanup. One major benefit is the cleanup and re-running of useEffect when specific properties change. This eliminates the need of storing the timeout id as a static property. It also eliminates imperative prop comparisons in `componentDidUpdate`. Instead, the function declares the data that will trigger a re-run of this side effect. If `currIndex` or `isPlaying` change, the effect will re-run. First the function will run the cleanup function to clear the timeout, and then will run the effect again. This compounds when a component requires many side effects.
91+
92+
The `useEffect` hook brings more clarity and organization to the side effects in your components. Class based components force you to provide your set up and tear down across three separate life cycle methods: `componentDidMount`, `componentDidUpdate`, and `componentWillUnmount`. This often results in duplication of code. A component may use many side effects which are all placed in these same lifecycle methods. The `useEffect` Hook shifts this paradigm by isolating the functionality of a given side effect in one function. Clean up for the effect is set as a return function. This colocation allows for easier mental parsing and grouping functions by their area of concern.
93+
94+
An extra benefit of `useState` is the ability to reference the current state without additional syntax. With Hooks you can call `setCurrIndex((currIndex + 1) % slides.length)` rather than passing a function which returns the partial state object to update. This results in cleaner code, and eliminated the need for the side-loaded function for the Slideshow component. Converting the Slideshow from a render prop component to a custom Hook resulted in a net reduction of 76 lines of code. Other components within the site utilized class components to manage a single piece of state. Hooks allow you to reduce the boilerplate of a class based component in these circumstances. Functional components connect to state with one line of code.
95+
96+
## Structuring Hooks in Gatsby
97+
98+
Currently I am structuring all my custom hooks in a top level folder so that other components can import them from a central location. Pulling the React dependencies out of Gatsby in version 2 allows for using Hooks immediately. To begin using Hooks today, update React and React-DOM to 16.8.0. There are considerations to take with the `useEffect Hook`. If it references the window object, you need to check that window is defined to avoid Gatsby build errors. These effects would normally live in `componentDidMount` where the component hydrates in the DOM. Hooks are called in the build process. The Gatsby docs have great resources for [debugging HTML builds](docs/debugging-html-builds/) if you encounter this issue.
99+
100+
```javascript
101+
function useMediaQuery() {
102+
const [isMobile, setIsMobile] = useState(false)
103+
104+
const handleSizeChange = ({ matches }) => setIsMobile(matches)
105+
106+
useEffect(() => {
107+
// Window does not exist on SSR
108+
if (typeof window !== "undefined") {
109+
const mql = window.matchMedia("(max-width: 650px)")
110+
mql.addListener(handleSizeChange)
111+
setIsMobile(mql.matches) // Set initial state in DOM
112+
113+
return () => mql.removeListener(handleSizeChange)
114+
}
115+
}, [])
116+
117+
return { isMobile }
118+
}
119+
```
120+
121+
Hooks bring many benefits to React functional components. Specific benefits include:
122+
123+
- Abstracting logic with custom Hooks over render props for reduced syntax and elements in devTools
124+
- Improved ability to share logic across components
125+
- Ability to call `useState` without the need for state updater functions if referencing the current version of state
126+
- Encapsulating side effects into their own function by area of concern
127+
- Provide clean up logic for side effects in a central location compared to across three lifecycle methods
128+
129+
Hooks may not be the death of class based components, but they do encourage critical thought before reaching for a class component. I believe that Hooks are a meaningful shift in the way we approach React development. I can't wait to see what you will build with Hooks.
630 KB
Loading
137 KB
Loading

0 commit comments

Comments
 (0)