1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00

Make next/link async safe (#4911)

Removes componentWillMount and uses memoize instead as recommended here: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
This commit is contained in:
Tim Neutkens 2018-08-06 22:44:18 -07:00 committed by GitHub
parent 3286ecb3fc
commit a528565c69
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -2,7 +2,6 @@
import { resolve, format, parse } from 'url' import { resolve, format, parse } from 'url'
import React, { Component, Children } from 'react' import React, { Component, Children } from 'react'
import {polyfill} from 'react-lifecycles-compat'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import exact from 'prop-types-exact' import exact from 'prop-types-exact'
import Router, { _rewriteUrlForNextExport } from './router' import Router, { _rewriteUrlForNextExport } from './router'
@ -18,6 +17,23 @@ function isLocal (href) {
const warnLink = execOnce(warn) const warnLink = execOnce(warn)
function memoizedFormatUrl (formatUrl) {
let lastHref = null
let lastAs = null
let lastResult = null
return (href, as) => {
if (href === lastHref && as === lastAs) {
return lastResult
}
const result = formatUrl(href, as)
lastHref = href
lastAs = as
lastResult = result
return result
}
}
class Link extends Component { class Link extends Component {
static propTypes = exact({ static propTypes = exact({
href: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, href: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
@ -41,11 +57,6 @@ class Link extends Component {
]).isRequired ]).isRequired
}) })
constructor (props, ...rest) {
super(props, ...rest)
this.formatUrls(props)
}
componentDidMount () { componentDidMount () {
this.prefetch() this.prefetch()
} }
@ -56,21 +67,18 @@ class Link extends Component {
} }
} }
// eslint-disable-next-line camelcase // The function is memoized so that no extra lifecycles are needed
UNSAFE_componentWillReceiveProps (nextProps) { // as per https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
this.formatUrls(nextProps) formatUrls = memoizedFormatUrl((href, asHref) => {
} return {
href: href && typeof href === 'object'
// We accept both 'href' and 'as' as objects which we can pass to `url.format`. ? format(href)
// We'll handle it here. : href,
formatUrls (props) { as: asHref && typeof asHref === 'object'
this.href = props.href && typeof props.href === 'object' ? format(asHref)
? format(props.href) : asHref
: props.href }
this.as = props.as && typeof props.as === 'object' })
? format(props.as)
: props.as
}
linkClicked = e => { linkClicked = e => {
const { nodeName, target } = e.currentTarget const { nodeName, target } = e.currentTarget
@ -80,8 +88,7 @@ class Link extends Component {
return return
} }
const { shallow } = this.props let { href, as } = this.formatUrls(this.props.href, this.props.as)
let { href, as } = this
if (!isLocal(href)) { if (!isLocal(href)) {
// ignore click if it's outside our scope // ignore click if it's outside our scope
@ -105,7 +112,7 @@ class Link extends Component {
const changeMethod = replace ? 'replace' : 'push' const changeMethod = replace ? 'replace' : 'push'
// straight up redirect // straight up redirect
Router[changeMethod](href, as, { shallow }) Router[changeMethod](href, as, { shallow: this.props.shallow })
.then((success) => { .then((success) => {
if (!success) return if (!success) return
if (scroll) { if (scroll) {
@ -124,13 +131,14 @@ class Link extends Component {
// Prefetch the JSON page if asked (only in the client) // Prefetch the JSON page if asked (only in the client)
const { pathname } = window.location const { pathname } = window.location
const href = resolve(pathname, this.href) const {href: parsedHref} = this.formatUrls(this.props.href, this.props.as)
const href = resolve(pathname, parsedHref)
Router.prefetch(href) Router.prefetch(href)
} }
render () { render () {
let { children } = this.props let { children } = this.props
let { href, as } = this let { href, as } = this.formatUrls(this.props.href, this.props.as)
// Deprecated. Warning shown by propType check. If the childen provided is a string (<Link>example</Link>) we wrap it in an <a> tag // Deprecated. Warning shown by propType check. If the childen provided is a string (<Link>example</Link>) we wrap it in an <a> tag
if (typeof children === 'string') { if (typeof children === 'string') {
children = <a>{children}</a> children = <a>{children}</a>
@ -169,6 +177,4 @@ class Link extends Component {
} }
} }
// Make UNSAFE_ compatible with version of React under 16.3
polyfill(Link)
export default Link export default Link