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:
parent
3286ecb3fc
commit
a528565c69
62
lib/link.js
62
lib/link.js
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue