Skip to content

Commit

Permalink
add prefetch-on-hover #157
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesknelson committed Dec 26, 2019
1 parent fda01b2 commit e56b216
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 34 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 3,7 @@
"devDependencies": {
"lerna": "^3.15.0",
"@types/jest": "^23.3.7",
"@types/node": "10.12.18",
"@types/node": "13.1.0",
"@types/react": "^16.8.1",
"@types/react-dom": "^16.0.11",
"@frontarm/doc": "^0.5.0",
Expand All @@ -20,7 20,7 @@
"rollup-plugin-replace": "^2.1.0",
"rollup-plugin-terser": "^4.0.3",
"ts-jest": "^24.0.2",
"typescript": "^3.2.1"
"typescript": "^3.7.4"
},
"scripts": {
"build": "lerna run --stream build",
Expand Down
120 changes: 96 additions & 24 deletions packages/react-navi/src/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 16,10 @@ export interface UseLinkPropsOptions {
disabled?: boolean
hashScrollBehavior?: HashScrollBehavior
href: string | Partial<URLDescriptor>
prefetch?: boolean
prefetch?: boolean | 'hover' | 'mount'
state?: object
onClick?: React.MouseEventHandler<HTMLAnchorElement>
onMouseEnter?: React.MouseEventHandler<HTMLAnchorElement>
}

function isExternalHref(href) {
Expand Down Expand Up @@ -94,17 95,45 @@ export const useLinkProps = ({
prefetch,
state,
onClick,
onMouseEnter,
}: UseLinkPropsOptions) => {
let hashScrollBehaviorFromContext = React.useContext(HashScrollContext)
let context = React.useContext(NaviContext)
let navigation = context.navigation
if (prefetch && state) {
prefetch = false

if (process.env.NODE_ENV !== 'production') {
console.warn(
`Warning: A <Link> component received both "prefetch" and "state" `
`props, but links with state cannot be prefetched. Skipping prefetch.`,
)
}
}

if (prefetch === true) {
prefetch = 'mount'

if (process.env.NODE_ENV !== 'production') {
console.warn(
`Warning: A <Link> component received a "prefetch" value of "true". `
`This value is no longer supported - please set it to "mount" instead.`,
)
}
}

// Prefetch on hover by default.
if (prefetch === undefined) {
prefetch = 'hover'
}

const hashScrollBehaviorFromContext = React.useContext(HashScrollContext)
const context = React.useContext(NaviContext)
const navigation = context.navigation

if (hashScrollBehavior === undefined) {
hashScrollBehavior = hashScrollBehaviorFromContext
}

let route = context.steadyRoute || context.busyRoute
let routeURL = route && route.url
const route = context.steadyRoute || context.busyRoute
const routeURL = React.useMemo(() => route && route.url, [route?.url.href])
let linkURL = getLinkURL(href, routeURL)

if (!isExternalHref(href)) {
Expand All @@ -117,17 146,58 @@ export const useLinkProps = ({
linkURL = createURLDescriptor(resolvedHref)
}

// We need a URL descriptor that stays referentially equal so that we don't
// trigger prefetches more than we'd like.
const memoizedLinkURL = React.useMemo(() => linkURL, [linkURL?.href])

let doPrefetch = React.useMemo(() => {
let hasPrefetched = false

return () => {
if (
!hasPrefetched &&
memoizedLinkURL &&
memoizedLinkURL.pathname &&
navigation
) {
hasPrefetched = true
navigation.prefetch(memoizedLinkURL).catch(e => {
console.warn(
`A <Link> tried to prefetch "${
memoizedLinkURL!.pathname
}", but the ` `router was unable to fetch this path.`,
)
})
}
}
}, [memoizedLinkURL, navigation])

// Prefetch on mount if required, or if `prefetch` becomes `true`.
React.useEffect(() => {
if (prefetch && navigation && linkURL && linkURL.pathname) {
navigation.prefetch(linkURL).catch(e => {
console.warn(
`A <Link> tried to prefetch "${linkURL!.pathname}", but the `
`router was unable to fetch this path.`,
)
})
if (prefetch === true) {
doPrefetch()
}
}, [navigation, prefetch, linkURL && linkURL.href])
}, [prefetch, doPrefetch])

let handleMouseEnter = React.useCallback(
(event: React.MouseEvent<HTMLAnchorElement>) => {
if (prefetch === 'hover') {
if (onMouseEnter) {
onMouseEnter(event)
}

if (disabled) {
event.preventDefault()
return
}

if (!event.defaultPrevented) {
doPrefetch()
}
}
},
[disabled, doPrefetch, onMouseEnter, prefetch],
)

let handleClick = React.useCallback(
(event: React.MouseEvent<HTMLAnchorElement>) => {
Expand Down Expand Up @@ -176,20 246,18 @@ export const useLinkProps = ({

return {
onClick: handleClick,
onMouseEnter: handleMouseEnter,
href: linkURL ? linkURL.href : (href as string),
}
}

export interface LinkProps
extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
extends UseLinkPropsOptions,
Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
active?: boolean
activeClassName?: string
activeStyle?: object
disabled?: boolean
exact?: boolean
hashScrollBehavior?: HashScrollBehavior
href: string | Partial<URLDescriptor>
prefetch?: boolean
ref?: React.Ref<HTMLAnchorElement>

render?: (props: LinkRendererProps) => any
Expand Down Expand Up @@ -300,31 368,35 @@ export const Link:
hashScrollBehavior,
href: hrefProp,
onClick: onClickProp,
onMouseEnter: onMouseEnterProp,
prefetch,
render,
state,
...rest
} = props

let { onClick, href } = useLinkProps({
let linkProps = useLinkProps({
hashScrollBehavior,
href: hrefProp,
onClick: onClickProp,
onMouseEnter: onMouseEnterProp,
prefetch,
state,
})

let actualActive = useActive(href, { exact: !!exact })
let actualActive = useActive(linkProps.href, { exact: !!exact })
if (active === undefined) {
active = actualActive
}

let context = {
...rest,
...linkProps,
children,
href,
ref: anchorRef,

// Don't capture clicks on links with a `target` prop.
onClick: props.target ? onClickProp : onClick,
onClick: props.target ? onClickProp : linkProps.onClick,
}

React.useEffect(() => {
Expand All @@ -350,7 422,7 @@ export const Link:
disabled: props.disabled,
tabIndex: props.tabIndex,
hidden: props.hidden,
href: href,
href: linkProps.href,
id: props.id,
lang: props.lang,
style: props.style,
Expand Down
23 changes: 15 additions & 8 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1741,10 1741,10 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.3.tgz#7c6b0f8eaf16ae530795de2ad1b85d34bf2f5c58"
integrity sha512-wp6IOGu1lxsfnrD 5mX6qwSwWuqsdkKKxTN4aQc4wByHAKZJf9/D4KXPQ1POUjEbnCP5LMggB0OEFNY9OTsMqg==

"@types/node@10.12.18":
version "10.12.18"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67"
integrity sha512-fh pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753 TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ PQNDdWQaQ==
"@types/node@13.1.0":
version "13.1.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.0.tgz#225cbaac5fdb2b9ac651b02c070d8aa3c37cc812"
integrity sha512-zwrxviZS08kRX40nqBrmERElF2vpw4IUTd5khkhBTfFH8AOaeoLVx48EC4 ZzS2/Iga7NevncqnsUSYjM4OWYA==

"@types/prop-types@*":
version "15.7.0"
Expand All @@ -1758,6 1758,13 @@
dependencies:
"@types/react" "*"

"@types/react-helmet@^5.0.8":
version "5.0.14"
resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-5.0.14.tgz#a07979ccb2cee088e74e735c84058fc8607d32e4"
integrity sha512-Q73FFg7 LjblfSQUNbnjrwy2T1avBP8yevEgNrkDjyz1rBbnXkuOQcEV7I5wvmAic9FLUk0CnkLieEDej84Zkw==
dependencies:
"@types/react" "*"

"@types/react@*", "@types/react@^16.8.1":
version "16.8.8"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.8.tgz#4b60a469fd2469f7aa6eaa0f8cfbc51f6d76e662"
Expand Down Expand Up @@ -7808,10 7815,10 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=

typescript@^3.2.1:
version "3.3.3333"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.3333.tgz#171b2c5af66c59e9431199117a3bcadc66fdcfd6"
integrity sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw==
typescript@^3.7.4:
version "3.7.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.4.tgz#1743a5ec5fef6a1fa9f3e4708e33c81c73876c19"
integrity sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw==

uglify-js@^3.1.4:
version "3.4.9"
Expand Down

0 comments on commit e56b216

Please sign in to comment.