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 React, { Component, Children } from 'react'
import {polyfill} from 'react-lifecycles-compat'
import PropTypes from 'prop-types'
import exact from 'prop-types-exact'
import Router, { _rewriteUrlForNextExport } from './router'
@ -18,6 +17,23 @@ function isLocal (href) {
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 {
static propTypes = exact({
href: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
@ -41,11 +57,6 @@ class Link extends Component {
]).isRequired
})
constructor (props, ...rest) {
super(props, ...rest)
this.formatUrls(props)
}
componentDidMount () {
this.prefetch()
}
@ -56,21 +67,18 @@ class Link extends Component {
}
}
// eslint-disable-next-line camelcase
UNSAFE_componentWillReceiveProps (nextProps) {
this.formatUrls(nextProps)
}
// We accept both 'href' and 'as' as objects which we can pass to `url.format`.
// We'll handle it here.
formatUrls (props) {
this.href = props.href && typeof props.href === 'object'
? format(props.href)
: props.href
this.as = props.as && typeof props.as === 'object'
? format(props.as)
: props.as
}
// The function is memoized so that no extra lifecycles are needed
// as per https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
formatUrls = memoizedFormatUrl((href, asHref) => {
return {
href: href && typeof href === 'object'
? format(href)
: href,
as: asHref && typeof asHref === 'object'
? format(asHref)
: asHref
}
})
linkClicked = e => {
const { nodeName, target } = e.currentTarget
@ -80,8 +88,7 @@ class Link extends Component {
return
}
const { shallow } = this.props
let { href, as } = this
let { href, as } = this.formatUrls(this.props.href, this.props.as)
if (!isLocal(href)) {
// ignore click if it's outside our scope
@ -105,7 +112,7 @@ class Link extends Component {
const changeMethod = replace ? 'replace' : 'push'
// straight up redirect
Router[changeMethod](href, as, { shallow })
Router[changeMethod](href, as, { shallow: this.props.shallow })
.then((success) => {
if (!success) return
if (scroll) {
@ -124,13 +131,14 @@ class Link extends Component {
// Prefetch the JSON page if asked (only in the client)
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)
}
render () {
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
if (typeof children === 'string') {
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