Skip to content

Commit

Permalink
default useLocationChange to skip initial render (#89)
Browse files Browse the repository at this point in the history
* default useLocationChange to skip initial render

* update changelog

* clarify changelog fix for behavior reversion
  • Loading branch information
kyeotic authored May 2, 2021
1 parent a3d3edf commit 377f546
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 7 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.1.0] - 2021-05-02
### Added
- `options.onInitial` parameter for `useLocationChange` that controls the first render behavior. `default: false`.
### Fixed
- `useLocationChange` invoking the setter on initial render. This was not intended and was an unannounced change from the v1 behavior, so reverting it is not considered an API change but a bugfix.

## [2.0.2] - 2021-03-22
### Added
Expand Down
3 changes: 3 additions & 0 deletions docs/api/useLocationChange.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ export function useLocationChange(
inheritBasePath: boolean
basePath: string
isActive: () => boolean | boolean
onInitial?: boolean
}
): void
```

**Note**: `options.inheritBasePath` defaults to `true` (even if `options` is not provided), and takes precedence over `options.basePath` if `true`. If no BasePath is in the context to inherit `options.basePath` will be used as a fallback, if present. If `basePath` is provided, either by parameter or by context, and is missing from the current path `null` is sent to the `setFn` callback.

By default this hook will not run on the initial mount for the component. You can get the location on the first render (mount) by setting `onInitial: true` in the `options` argument.

## Basic

The first parameter is a setter-function that is invoked with the new path whenever the url is changed. It does not automatically cause a re-render of the parent component (see _re-rendering_ below).
Expand Down
1 change: 1 addition & 0 deletions docs/themes/learn
Submodule learn added at 41c6cc
9 changes: 9 additions & 0 deletions src/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useLayoutEffect, useRef } from 'react'

export function useMountedLayout(fn, deps, { onInitial = false } = {}) {
const hasMounted = useRef(onInitial)
useLayoutEffect(() => {
if (!hasMounted.current) hasMounted.current = true
else fn()
}, deps)
}
15 changes: 10 additions & 5 deletions src/path.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
useLayoutEffect
} from 'react'
import { BasePathContext, PathContext } from './context.js'
import { useMountedLayout } from './hooks.js'
import { isNode, getSsrPath } from './node.js'
import { isFunction } from './typeChecks.js'

Expand Down Expand Up @@ -62,7 +63,7 @@ export function getCurrentHash() {

export function useLocationChange(
setFn,
{ inheritBasePath = true, basePath = '', isActive } = {}
{ inheritBasePath = true, basePath = '', isActive, onInitial = false } = {}
) {
if (isNode) return
const routerBasePath = useBasePath()
Expand Down Expand Up @@ -92,10 +93,14 @@ export function useLocationChange(

// When the basePath changes re-check the path after the render completes
// This allows nested contexts to get an up-to-date formatted path
useLayoutEffect(() => {
if (isActive !== undefined && !isPredicateActive(isActive)) return
setRef.current(getFormattedPath(basePath))
}, [basePath, isActive])
useMountedLayout(
() => {
if (isActive !== undefined && !isPredicateActive(isActive)) return
setRef.current(getFormattedPath(basePath))
},
[basePath, isActive],
{ onInitial }
)
}

/**
Expand Down
16 changes: 14 additions & 2 deletions test/path.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,22 @@ beforeEach(() => {
})

describe('useLocationChange', () => {
function Route({ onChange, isActive, basePath }) {
useLocationChange(onChange, { isActive, basePath })
function Route({ onChange, isActive, basePath, onInitial = false }) {
useLocationChange(onChange, { isActive, basePath, onInitial })
return null
}
test("setter doesn't get updated on mount", async () => {
let watcher = jest.fn()
render(<Route onChange={watcher} />)

expect(watcher).not.toBeCalled()
})
test('setter is updated on mount when onInitial is true', async () => {
let watcher = jest.fn()
render(<Route onChange={watcher} onInitial />)

expect(watcher).toBeCalled()
})
test('setter gets updated path', async () => {
let watcher = jest.fn()
render(<Route onChange={watcher} />)
Expand Down

0 comments on commit 377f546

Please sign in to comment.