diff --git a/lib/link.js b/lib/link.js index 2e8132bd..d9897847 100644 --- a/lib/link.js +++ b/lib/link.js @@ -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 (example) we wrap it in an tag if (typeof children === 'string') { children = {children} @@ -169,6 +177,4 @@ class Link extends Component { } } -// Make UNSAFE_ compatible with version of React under 16.3 -polyfill(Link) export default Link