navigation-stack 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +144 -282
- package/karma.conf.cjs +1 -1
- package/lib/cjs/NavigationStack.js +138 -49
- package/lib/cjs/data-storage/DataStorage.js +7 -6
- package/lib/cjs/environment/InMemoryEnvironment.js +6 -0
- package/lib/cjs/{session/ServerSideRenderSession.js → environment/ServerSideRenderEnvironment.js} +5 -6
- package/lib/cjs/environment/WebBrowserEnvironment.js +6 -0
- package/lib/cjs/environment/log/InMemoryLog.js +23 -0
- package/lib/cjs/environment/log/WebBrowserLog.js +22 -0
- package/lib/cjs/{session → environment}/navigation/InMemoryNavigation.js +16 -5
- package/lib/cjs/{session → environment}/navigation/ServerSideNavigation.js +16 -7
- package/lib/cjs/{session → environment}/navigation/WebBrowserNavigation.js +48 -8
- package/lib/cjs/{session/navigation/error/ServerSideNavigationError.js → environment/navigation/error/ServerSideRedirectError.js} +2 -2
- package/lib/cjs/environment/scroll-position/WebBrowserScrollPosition.js +15 -0
- package/lib/cjs/getLocationBaseFromLocation.js +14 -0
- package/lib/cjs/getLocationUrl.js +3 -5
- package/lib/cjs/index.js +10 -16
- package/lib/cjs/navigationBlockers.js +34 -32
- package/lib/cjs/navigationBlockersEvaluation.js +150 -0
- package/lib/cjs/parseInputLocation.js +2 -2
- package/lib/cjs/parseQueryFromSearch.js +3 -6
- package/lib/cjs/parseQueryString.js +77 -0
- package/lib/cjs/scroll-position/ScrollPositionAutoSaver.js +7 -6
- package/lib/cjs/scroll-position/ScrollPositionRestoration.js +31 -27
- package/lib/cjs/scroll-position/ScrollPositionSaver.js +6 -4
- package/lib/cjs/session/Session.js +61 -26
- package/lib/cjs/session/subscription/Subscription.js +36 -18
- package/lib/cjs/stringifyQuery.js +66 -0
- package/lib/cjs/stringifyQueryAsSearch.js +14 -0
- package/lib/esm/NavigationStack.js +138 -49
- package/lib/esm/data-storage/DataStorage.js +7 -6
- package/lib/esm/environment/InMemoryEnvironment.js +6 -0
- package/lib/esm/environment/ServerSideRenderEnvironment.js +10 -0
- package/lib/esm/environment/WebBrowserEnvironment.js +6 -0
- package/lib/esm/environment/log/InMemoryLog.js +17 -0
- package/lib/esm/environment/log/WebBrowserLog.js +16 -0
- package/lib/esm/{session → environment}/navigation/InMemoryNavigation.js +16 -5
- package/lib/esm/{session → environment}/navigation/ServerSideNavigation.js +16 -7
- package/lib/esm/{session → environment}/navigation/WebBrowserNavigation.js +48 -8
- package/lib/esm/{session/navigation/error/ServerSideNavigationError.js → environment/navigation/error/ServerSideRedirectError.js} +1 -1
- package/lib/esm/environment/scroll-position/WebBrowserScrollPosition.js +15 -0
- package/lib/esm/getLocationBaseFromLocation.js +9 -0
- package/lib/esm/getLocationUrl.js +2 -5
- package/lib/esm/index.js +5 -8
- package/lib/esm/navigationBlockers.js +34 -32
- package/lib/esm/navigationBlockersEvaluation.js +145 -0
- package/lib/esm/parseInputLocation.js +2 -2
- package/lib/esm/parseQueryFromSearch.js +2 -6
- package/lib/esm/parseQueryString.js +72 -0
- package/lib/esm/scroll-position/ScrollPositionAutoSaver.js +7 -6
- package/lib/esm/scroll-position/ScrollPositionRestoration.js +31 -27
- package/lib/esm/scroll-position/ScrollPositionSaver.js +6 -4
- package/lib/esm/session/Session.js +61 -26
- package/lib/esm/session/subscription/Subscription.js +36 -18
- package/lib/esm/stringifyQuery.js +61 -0
- package/lib/esm/stringifyQueryAsSearch.js +8 -0
- package/lib/index.d.ts +180 -34
- package/package.json +4 -7
- package/src/NavigationStack.js +166 -56
- package/src/data-storage/DataStorage.js +9 -6
- package/src/environment/InMemoryEnvironment.js +6 -0
- package/src/environment/ServerSideRenderEnvironment.js +10 -0
- package/src/environment/WebBrowserEnvironment.js +6 -0
- package/src/environment/log/InMemoryLog.js +20 -0
- package/src/environment/log/WebBrowserLog.js +18 -0
- package/src/{session → environment}/navigation/InMemoryNavigation.js +16 -5
- package/src/{session → environment}/navigation/ServerSideNavigation.js +16 -7
- package/src/{session → environment}/navigation/WebBrowserNavigation.js +48 -8
- package/src/{session/navigation/error/ServerSideNavigationError.js → environment/navigation/error/ServerSideRedirectError.js} +1 -1
- package/src/environment/scroll-position/WebBrowserScrollPosition.js +15 -0
- package/src/getLocationBaseFromLocation.js +7 -0
- package/src/getLocationUrl.js +2 -5
- package/src/index.js +10 -13
- package/src/navigationBlockers.js +55 -34
- package/src/navigationBlockersEvaluation.js +161 -0
- package/src/parseInputLocation.js +2 -2
- package/src/parseQueryFromSearch.js +2 -6
- package/src/parseQueryString.js +81 -0
- package/src/scroll-position/ScrollPositionAutoSaver.js +10 -6
- package/src/scroll-position/ScrollPositionRestoration.js +36 -30
- package/src/scroll-position/ScrollPositionSaver.js +6 -4
- package/src/scroll-position/index.js +1 -1
- package/src/session/Session.js +68 -24
- package/src/session/subscription/Subscription.js +36 -11
- package/src/stringifyQuery.js +71 -0
- package/src/stringifyQueryAsSearch.js +9 -0
- package/test/NavigationStack.addBasePath.test.js +50 -0
- package/test/{redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.test.js → NavigationStack.blockNonProgrammaticNavigationIfRequired.test.js} +51 -63
- package/test/{redux/middleware/createProgrammaticNavigationBlockerMiddleware.test.js → NavigationStack.blockProgrammaticNavigationIfRequired.test.js} +98 -78
- package/test/NavigationStack.general.test.js +68 -0
- package/test/NavigationStack.parseInputLocation.test.js +52 -0
- package/test/NavigationStack.removeBasePath.test.js +69 -0
- package/test/NavigationStack.test.js +97 -29
- package/test/data-storage/LocationDataStorage.test.js +3 -2
- package/test/index.js +7 -31
- package/test/index.test.js +4 -5
- package/test/parseQueryFromSearch.test.js +19 -0
- package/test/parseQueryString.test.js +18 -0
- package/test/scroll-position/ScrollPositionRestoration.test.js +34 -13
- package/test/scroll-position/createApp.js +8 -8
- package/test/scroll-position/withScrollableContainerAtIndexPageWithDisabledAutomaticScrollPositionRestoration.js +4 -4
- package/test/session/{InMemorySession.test.js → Session.InMemoryEnvironment.test.js} +10 -9
- package/test/session/{ServerSession.test.js → Session.ServerSideRenderEnvironment.test.js} +5 -4
- package/test/session/{WebBrowserSession.test.js → Session.WebBrowserEnvironment.test.js} +63 -13
- package/test/shouldWarn.js +44 -0
- package/test/stringifyQuery.test.js +65 -0
- package/types/index.d.ts +180 -34
- package/types/tsconfig.json +0 -1
- package/data-storage/package.json +0 -7
- package/lib/cjs/createSearchFromQuery.js +0 -13
- package/lib/cjs/debug.js +0 -12
- package/lib/cjs/redux/ActionTypes.js +0 -14
- package/lib/cjs/redux/ActionTypesInternal.js +0 -8
- package/lib/cjs/redux/Actions.js +0 -28
- package/lib/cjs/redux/createMiddlewares.js +0 -60
- package/lib/cjs/redux/index.js +0 -13
- package/lib/cjs/redux/internalLocationReducer.js +0 -14
- package/lib/cjs/redux/locationReducer.js +0 -13
- package/lib/cjs/redux/middleware/createAddInputLocationBasePathMiddleware.js +0 -32
- package/lib/cjs/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +0 -113
- package/lib/cjs/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +0 -94
- package/lib/cjs/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +0 -30
- package/lib/cjs/redux/middleware/createUpdateInternalLocationMiddleware.js +0 -73
- package/lib/cjs/redux/middleware/navigationOperationMiddleware.js +0 -40
- package/lib/cjs/redux/middleware/parseInputLocationMiddleware.js +0 -29
- package/lib/cjs/redux/middleware/updateLocationMiddleware.js +0 -34
- package/lib/cjs/session/InMemorySession.js +0 -22
- package/lib/cjs/session/WebBrowserSession.js +0 -20
- package/lib/data-storage/index.d.ts +0 -35
- package/lib/esm/createSearchFromQuery.js +0 -8
- package/lib/esm/debug.js +0 -7
- package/lib/esm/redux/ActionTypes.js +0 -9
- package/lib/esm/redux/ActionTypesInternal.js +0 -3
- package/lib/esm/redux/Actions.js +0 -22
- package/lib/esm/redux/createMiddlewares.js +0 -54
- package/lib/esm/redux/index.js +0 -4
- package/lib/esm/redux/internalLocationReducer.js +0 -8
- package/lib/esm/redux/locationReducer.js +0 -7
- package/lib/esm/redux/middleware/createAddInputLocationBasePathMiddleware.js +0 -27
- package/lib/esm/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +0 -108
- package/lib/esm/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +0 -88
- package/lib/esm/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +0 -25
- package/lib/esm/redux/middleware/createUpdateInternalLocationMiddleware.js +0 -68
- package/lib/esm/redux/middleware/navigationOperationMiddleware.js +0 -35
- package/lib/esm/redux/middleware/parseInputLocationMiddleware.js +0 -24
- package/lib/esm/redux/middleware/updateLocationMiddleware.js +0 -28
- package/lib/esm/session/InMemorySession.js +0 -15
- package/lib/esm/session/ServerSideRenderSession.js +0 -11
- package/lib/esm/session/WebBrowserSession.js +0 -13
- package/lib/redux/index.d.ts +0 -90
- package/lib/scroll-position/index.d.ts +0 -107
- package/redux/package.json +0 -7
- package/scroll-position/package.json +0 -7
- package/src/createSearchFromQuery.js +0 -9
- package/src/debug.js +0 -8
- package/src/redux/ActionTypes.js +0 -9
- package/src/redux/ActionTypesInternal.js +0 -3
- package/src/redux/Actions.js +0 -27
- package/src/redux/createMiddlewares.js +0 -65
- package/src/redux/index.js +0 -4
- package/src/redux/internalLocationReducer.js +0 -9
- package/src/redux/locationReducer.js +0 -8
- package/src/redux/middleware/createAddInputLocationBasePathMiddleware.js +0 -27
- package/src/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +0 -119
- package/src/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +0 -94
- package/src/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +0 -26
- package/src/redux/middleware/createUpdateInternalLocationMiddleware.js +0 -72
- package/src/redux/middleware/navigationOperationMiddleware.js +0 -34
- package/src/redux/middleware/parseInputLocationMiddleware.js +0 -23
- package/src/redux/middleware/updateLocationMiddleware.js +0 -28
- package/src/session/InMemorySession.js +0 -13
- package/src/session/ServerSideRenderSession.js +0 -9
- package/src/session/WebBrowserSession.js +0 -13
- package/test/middlewareTestUtil.js +0 -31
- package/test/redux/Action.test.js +0 -73
- package/test/redux/ActionTypes.test.js +0 -13
- package/test/redux/createMiddlewares.test.js +0 -96
- package/test/redux/index.test.js +0 -10
- package/test/redux/locationReducer.test.js +0 -39
- package/test/redux/middleware/createAddInputLocationBasePathMiddleware.test.js +0 -40
- package/test/redux/middleware/createRemoveOutputLocationBasePathMiddleware.test.js +0 -51
- package/test/redux/middleware/navigationOperationMiddleware.test.js +0 -78
- package/test/redux/middleware/parseInputLocationMiddleware.test.js +0 -62
- package/test/testUtil.js +0 -3
- package/types/data-storage/index.d.ts +0 -35
- package/types/redux/index.d.ts +0 -90
- package/types/scroll-position/index.d.ts +0 -107
- /package/lib/cjs/{session → environment}/lifecycle/InMemorySessionLifecycle.js +0 -0
- /package/lib/cjs/{session → environment}/lifecycle/WebBrowserSessionLifecycle.js +0 -0
- /package/lib/cjs/{session → environment}/lifecycle/page-lifecycle/PageLifecycle.js +0 -0
- /package/lib/cjs/{session → environment}/lifecycle/page-lifecycle/PageLifecycleInstance.js +0 -0
- /package/lib/cjs/{session → environment}/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +0 -0
- /package/lib/cjs/{session → environment}/navigation/error/NavigationOutOfBoundsError.js +0 -0
- /package/lib/cjs/{session → environment}/navigation/operation/operations.js +0 -0
- /package/lib/esm/{session → environment}/lifecycle/InMemorySessionLifecycle.js +0 -0
- /package/lib/esm/{session → environment}/lifecycle/WebBrowserSessionLifecycle.js +0 -0
- /package/lib/esm/{session → environment}/lifecycle/page-lifecycle/PageLifecycle.js +0 -0
- /package/lib/esm/{session → environment}/lifecycle/page-lifecycle/PageLifecycleInstance.js +0 -0
- /package/lib/esm/{session → environment}/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +0 -0
- /package/lib/esm/{session → environment}/navigation/error/NavigationOutOfBoundsError.js +0 -0
- /package/lib/esm/{session → environment}/navigation/operation/operations.js +0 -0
- /package/src/{session → environment}/lifecycle/InMemorySessionLifecycle.js +0 -0
- /package/src/{session → environment}/lifecycle/WebBrowserSessionLifecycle.js +0 -0
- /package/src/{session → environment}/lifecycle/page-lifecycle/PageLifecycle.js +0 -0
- /package/src/{session → environment}/lifecycle/page-lifecycle/PageLifecycleInstance.js +0 -0
- /package/src/{session → environment}/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +0 -0
- /package/src/{session → environment}/navigation/error/NavigationOutOfBoundsError.js +0 -0
- /package/src/{session → environment}/navigation/operation/operations.js +0 -0
- /package/test/{parseInputLocationMiddleware.test.js → parseInputLocation.test.js} +0 -0
package/README.md
CHANGED
|
@@ -3,16 +3,18 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/navigation-stack)
|
|
4
4
|
[](https://www.npmjs.com/package/navigation-stack)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Navigation in a Single-Page Application.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
* Represents web browser navigation history as a "stack" data structure.
|
|
9
|
+
* Provides operations to perform programmatic navigation such as "push" (go to new URL), "replace" (redirect to new URL), "shift" (rewind to a previously visited URL).
|
|
10
|
+
* Provides a subscription mechanism to get notified on location changes.
|
|
11
|
+
* Supports automatic [scroll position restoration](#scroll-position-restoration) on "Back"/"Forward" navigation.
|
|
12
|
+
* If you're using React, see [`navigation-stack-react`](http://npmjs.com/package/navigation-stack-react) package.
|
|
11
13
|
|
|
12
14
|
## Install
|
|
13
15
|
|
|
14
16
|
```
|
|
15
|
-
npm install navigation-stack
|
|
17
|
+
npm install navigation-stack --save
|
|
16
18
|
```
|
|
17
19
|
|
|
18
20
|
## Use
|
|
@@ -22,17 +24,17 @@ Any changes to a `NavigationStack` instance are "magically" reflected in the web
|
|
|
22
24
|
Start by creating a `NavigationStack` instance.
|
|
23
25
|
|
|
24
26
|
```js
|
|
25
|
-
import { NavigationStack,
|
|
27
|
+
import { NavigationStack, WebBrowserEnvironment } from 'navigation-stack'
|
|
26
28
|
|
|
27
29
|
// Create a `NavigationStack` instance.
|
|
28
|
-
|
|
29
|
-
const navigationStack = new NavigationStack(new WebBrowserSession())
|
|
30
|
+
const navigationStack = new NavigationStack(WebBrowserEnvironment)
|
|
30
31
|
```
|
|
31
32
|
|
|
32
33
|
Then subscribe to changes:
|
|
33
34
|
|
|
34
35
|
```js
|
|
35
36
|
// Subscribe to location changes.
|
|
37
|
+
// The listener function will be called immediately after the current location has changed.
|
|
36
38
|
// The first call happens for the initial location.
|
|
37
39
|
// Next calls will happen in case of navigation.
|
|
38
40
|
const unsubscribe = navigationStack.subscribe((location) => {
|
|
@@ -45,6 +47,7 @@ Now ready to perform navigation actions.
|
|
|
45
47
|
|
|
46
48
|
```js
|
|
47
49
|
// Sets the initial location.
|
|
50
|
+
// No argument when using `WebBrowserEnvironment`.
|
|
48
51
|
navigationStack.init()
|
|
49
52
|
|
|
50
53
|
// Sets the `location` to be a new location.
|
|
@@ -139,135 +142,63 @@ Current `location` object has all the properties of a [standard web browser loca
|
|
|
139
142
|
* `key: string` — A string ID of the location that is guaranteed to be unique within the session's limits and could be used as a "key" to store any supplementary data associated to this location.
|
|
140
143
|
* `index: number` — The index of the location in the navigation stack, starting with `0` for the initial location.
|
|
141
144
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
<!--
|
|
145
|
-
One could use Redux'es standard [subscription mechanisms](https://redux.js.org/api/store#subscribelistener) to immediately get notified of current location changes.
|
|
146
|
-
|
|
147
|
-
```js
|
|
148
|
-
let currentLocation
|
|
149
|
-
|
|
150
|
-
// Create a Redux store.
|
|
151
|
-
const store = createStore(
|
|
152
|
-
locationReducer, // Reducer function. For example, `locationReducer()`.
|
|
153
|
-
applyMiddleware(...createMiddlewares(new WebBrowserSession()))
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
// Subscribe to any potential Redux state changes.
|
|
157
|
-
const unsubscribe = store.subscribe(() => {
|
|
158
|
-
const previousLocation = currentLocation
|
|
159
|
-
currentLocation = store.getState() // In case of using `locationReducer()`.
|
|
160
|
-
if (currentLocation !== previousLocation) {
|
|
161
|
-
// The first time is for the initial location.
|
|
162
|
-
// Next times will happen in case of navigation.
|
|
163
|
-
console.log('Location has changed')
|
|
164
|
-
}
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
// Initialize navigation with an initial location.
|
|
168
|
-
//
|
|
169
|
-
// It will trigger the listener.
|
|
170
|
-
//
|
|
171
|
-
store.dispatch(Actions.init(window.location))
|
|
145
|
+
## Scroll Position Restoration
|
|
172
146
|
|
|
173
|
-
|
|
174
|
-
unsubscribe()
|
|
175
|
-
```
|
|
176
|
-
-->
|
|
147
|
+
By default, `NavigationStack` doesn't do anything with the scroll position when performing navigation. This means that it neither scrolls to the top of the page when calling `.push()` or `.replace()`, nor restores the previous scroll position on "Back" or "Forward" navigation, including `.shift()` navigation.
|
|
177
148
|
|
|
178
|
-
|
|
179
|
-
One could subscribe to location changes by calling `navigationStack.subscribe()`.
|
|
149
|
+
To fix that, enable automatic scroll position management feature by passing `manageScrollPosition: true` parameter when creating a `NavigationStack` instance, and then call `.locationRendered(location)` every time a different location has been rendered (including the initial location) immediately after it has been rendered.
|
|
180
150
|
|
|
181
151
|
```js
|
|
182
|
-
|
|
183
|
-
// It should be tied to a navigation "session".
|
|
184
|
-
const navigationStack = new NavigationStack(new WebBrowserSession())
|
|
152
|
+
import { NavigationStack, WebBrowserEnvironment } from 'navigation-stack'
|
|
185
153
|
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const unsubscribe = navigationStack.subscribe((location) => {
|
|
190
|
-
console.log('Current location', location)
|
|
154
|
+
// Create a `NavigationStack` instance with a `manageScrollPosition: true` option.
|
|
155
|
+
const navigationStack = new NavigationStack(WebBrowserEnvironment, {
|
|
156
|
+
manageScrollPosition: true
|
|
191
157
|
})
|
|
192
158
|
|
|
193
|
-
|
|
194
|
-
// It will trigger the listener.
|
|
195
|
-
navigationStack.push('/new-location')
|
|
196
|
-
|
|
197
|
-
// Stop listening to location changes.
|
|
198
|
-
unsubscribe()
|
|
199
|
-
```
|
|
200
|
-
-->
|
|
201
|
-
|
|
202
|
-
<!--
|
|
203
|
-
## Why Redux?
|
|
204
|
-
|
|
205
|
-
Why complicate things by providing "middlewares", "actions" and a "reducer" when it could be just a conventional API? That's because always knowing the "current location" means having to deal with "state management" in one way or another, and the simplest and most popular "state management" toolkit to date seems to be Redux.
|
|
206
|
-
|
|
207
|
-
If it was just about dispatching the `Actions` then of course it wouldn't require any "state management". But it's the "get current location" piece that changes the whole picture. One could say that using Redux for such a simple task is an overkill but actually reinventing a wheel is what I would consider "overkill". It's like crafting your own screwdriver just because the one from Walmart feels too bulky.
|
|
208
|
-
-->
|
|
209
|
-
|
|
210
|
-
## Scroll Position Restoration
|
|
159
|
+
//----------------------------------------------------------------------------------------
|
|
211
160
|
|
|
212
|
-
|
|
161
|
+
function onLocationChange(location) {
|
|
162
|
+
// Render the page.
|
|
163
|
+
if (location.pathname === '/initial') {
|
|
164
|
+
document.body.innerHTML = '<div> Initial Location </div>'
|
|
165
|
+
} else if (location.pathame === '/new') {
|
|
166
|
+
document.body.innerHTML = '<div> New Location </div>'
|
|
167
|
+
} else {
|
|
168
|
+
throw new Error(`Unknown location: ${location.pathname}`)
|
|
169
|
+
}
|
|
213
170
|
|
|
214
|
-
|
|
215
|
-
|
|
171
|
+
// As soon as a page has been rendered, without any delay, tell `NavigationStack` to restore
|
|
172
|
+
// a previously-saved scroll position, if there's any.
|
|
173
|
+
//
|
|
174
|
+
// This method must be called both for the initial location and any subsequent location.
|
|
175
|
+
//
|
|
176
|
+
navigationStack.locationRendered(location)
|
|
177
|
+
}
|
|
216
178
|
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
maintainScrollPosition: true
|
|
220
|
-
})
|
|
179
|
+
// Subscribe to location changes.
|
|
180
|
+
navigationStack.subscribe(onLocationChange)
|
|
221
181
|
|
|
222
182
|
//----------------------------------------------------------------------------------------
|
|
223
183
|
|
|
224
|
-
//
|
|
184
|
+
// Start at the current location which is assumed to be "/initial-location".
|
|
185
|
+
// No argument when using `WebBrowserEnvironment`.
|
|
225
186
|
navigationStack.init()
|
|
226
187
|
|
|
227
|
-
//
|
|
228
|
-
document.body.innerHTML = '<div> Initial Location </div>'
|
|
229
|
-
|
|
230
|
-
// As soon as a page has been rendered, without any delay, tell `NavigationStack` to restore
|
|
231
|
-
// a previously-saved scroll position, if there's any.
|
|
232
|
-
//
|
|
233
|
-
// This method must be called both for the initial location and any subsequent location.
|
|
234
|
-
//
|
|
235
|
-
navigationStack.locationRendered()
|
|
236
|
-
|
|
237
|
-
//----------------------------------------------------------------------------------------
|
|
238
|
-
|
|
239
|
-
// Set the `location` to be a new location.
|
|
188
|
+
// Set the `location` to be "/new-location".
|
|
240
189
|
//
|
|
241
190
|
// This also updates the URL in the web browser's address bar
|
|
242
191
|
// and adds a new entry in the web browser's navigation history.
|
|
243
192
|
//
|
|
244
193
|
navigationStack.push('/new-location')
|
|
245
194
|
|
|
246
|
-
//
|
|
247
|
-
document.body.innerHTML = '<div> New Location </div>'
|
|
248
|
-
|
|
249
|
-
// The new location is now rendered.
|
|
250
|
-
// Immediately after it has been rendered, call `.locationRenered()`.
|
|
251
|
-
// There's no scroll position to restore because it's not a previously-visited location.
|
|
252
|
-
navigationStack.locationRendered()
|
|
253
|
-
|
|
254
|
-
//----------------------------------------------------------------------------------------
|
|
255
|
-
|
|
256
|
-
// Set `location` "back" to the initial location.
|
|
195
|
+
// Set `location` "back" to "/initial-location".
|
|
257
196
|
//
|
|
258
197
|
// This also updates the URL in the web browser's address bar
|
|
259
198
|
// and repositions the "current location" pointer in the web browser's navigation history.
|
|
260
199
|
//
|
|
261
200
|
navigationStack.shift(-1)
|
|
262
201
|
|
|
263
|
-
// Render the initial location.
|
|
264
|
-
document.body.innerHTML = '<div> Initial Location </div>'
|
|
265
|
-
|
|
266
|
-
// The initial location is now rendered.
|
|
267
|
-
// Immediately after it has been rendered, call `.locationRenered()`.
|
|
268
|
-
// Restores the scroll position at the initial location.
|
|
269
|
-
navigationStack.locationRendered()
|
|
270
|
-
|
|
271
202
|
//----------------------------------------------------------------------------------------
|
|
272
203
|
|
|
273
204
|
// (optional)
|
|
@@ -278,11 +209,61 @@ navigationStack.locationRendered()
|
|
|
278
209
|
navigationStack.stop()
|
|
279
210
|
```
|
|
280
211
|
|
|
281
|
-
`NavigationStack`
|
|
212
|
+
`NavigationStack` constructor relevant options:
|
|
213
|
+
|
|
214
|
+
* `manageScrollPosition: true` — Enables the automatic scroll position management feature.
|
|
215
|
+
* `shouldChangePageScrollPositionOnLocationChange(prevLocation?, newLocation): boolean` — Decides whether page scroll position management should still be active for a given transition from `prevLocation` to `newLocation`. Is only relevant when `manageScrollPosition: true` option is passed to `NavigationStack` constructor. As the most obvious use case, it allows an application to selectively disable the effect of resetting page scroll position when replacing the URL with same pathname but different query parameters.
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
`NavigationStack` relevant methods:
|
|
219
|
+
|
|
220
|
+
* `addScrollableContainer(key: string, element: Element, options?: object)` — Use it in cases when it should restore not only the page scroll position but also the scroll position(s) of any other scrollable container(s). Returns a "remove scrollable container" function.
|
|
221
|
+
* `options` object could have properties:
|
|
222
|
+
* `shouldChangeScrollPositionOnLocationChange(prevLocation?, newLocation): boolean` — Decides whether scroll position management inside this scrollable container should still be active for a given transition from `prevLocation` to `newLocation`. Is only relevant when `manageScrollPosition: true` option is passed to `NavigationStack` constructor. As the most obvious use case, it allows an application to selectively disable the effect of resetting scroll position inside a scrollable container when replacing the URL with same pathname but different query parameters.
|
|
282
223
|
|
|
283
|
-
* `addScrollableContainer(key: string, element: Element)` — Use it in cases when it should restore not only the page scroll position but also the scroll position(s) of any other scrollable container(s). Returns a "remove scrollable container" function.
|
|
284
224
|
* `locationRendered()` — Call it every time a different location has been rendered, including the initial location, without any delay, i.e. immediately after a different location has been rendered.
|
|
285
225
|
|
|
226
|
+
By default, when restoring scroll position, it uses basic "immediate" scrolling. A developer could supply a custom `scrollPositionSetter` option with an implementation of custom scrolling behavior. For example, it could be some kind of "smooth" scrolling or something like that.
|
|
227
|
+
|
|
228
|
+
```js
|
|
229
|
+
new NavigationStack(WebBrowserEnvironment, {
|
|
230
|
+
manageScrollPosition: true,
|
|
231
|
+
scrollPositionSetter: SmoothScrollPositionSetter
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
class SmoothScrollPositionSetter {
|
|
235
|
+
// Sets scroll position of a page or a scrollable element.
|
|
236
|
+
// Returns a `Promise` that resolves when it has finished setting the scroll position.
|
|
237
|
+
async set(
|
|
238
|
+
// `scrollableContainer: Element`.
|
|
239
|
+
// This is the scrollable container whose scroll position should be set.
|
|
240
|
+
// * When setting page scroll position, `scrollableContainer` is `undefined`.
|
|
241
|
+
// * When setting scrollable element scroll position, `scrollableContainer` is the scrollable element.
|
|
242
|
+
scrollableContainer,
|
|
243
|
+
// `scrollPositionOrAnchor: string | [number, number]`.
|
|
244
|
+
// This is the scroll position to set.
|
|
245
|
+
// * When setting page scroll position, it could be either an anchor or numeric coordinates.
|
|
246
|
+
// * When setting scrollable element scroll position, it could only be numeric coordinates.
|
|
247
|
+
scrollPositionOrAnchor,
|
|
248
|
+
// `scrollPosition` provides various "helper" methods for setting scroll position according to the environment.
|
|
249
|
+
// For example, in the context of a `WebBrowserEnvironment`, it provides the methods for setting scroll position in a web browser.
|
|
250
|
+
scrollPositionHelper
|
|
251
|
+
) {
|
|
252
|
+
if (typeof scrollPositionOrAnchor === 'string') {
|
|
253
|
+
await smoothScrollToAnchor(scrollableContainer, scrollPositionOrAnchor)
|
|
254
|
+
} else {
|
|
255
|
+
await smoothScrollToCoordinates(scrollableContainer, scrollPositionOrAnchor)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Cancels any pending (or in-progress) setting of scroll position.
|
|
260
|
+
cancel() {
|
|
261
|
+
stopSmoothScrolling()
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
<!--
|
|
286
267
|
<details>
|
|
287
268
|
<summary>Using scroll position restoration feature without <code>NavigationStack</code></summary>
|
|
288
269
|
|
|
@@ -382,6 +363,7 @@ scrollPositionRestoration.stop()
|
|
|
382
363
|
* `locationRendered(location)` — Call it every time a different location has been rendered, including the initial location, without any delay, i.e. immediately after a different location has been rendered. The location argument must have a `key`.
|
|
383
364
|
* `stop()` — Stops scroll position restoration and clears any listeners or timers.
|
|
384
365
|
</details>
|
|
366
|
+
-->
|
|
385
367
|
|
|
386
368
|
## Base Path
|
|
387
369
|
|
|
@@ -396,26 +378,26 @@ createMiddlewares(session, { basePath?: '/base-path' })
|
|
|
396
378
|
If the web application is hosted under a certain URL prefix, it should be specified as a `basePath` parameter when creating a `NavigationStack` instance. This prefix will automatically be added to the URL in the web browser's address bar while the `location` object itself won't include it in the `pathname`.
|
|
397
379
|
|
|
398
380
|
```js
|
|
399
|
-
new NavigationStack(
|
|
381
|
+
new NavigationStack(WebBrowserEnvironment, { basePath: '/base-path' })
|
|
400
382
|
```
|
|
401
383
|
|
|
402
|
-
##
|
|
384
|
+
## Environment
|
|
403
385
|
|
|
404
|
-
|
|
386
|
+
An "environment" class ties `NavigationStack` to the physical environment it operates in, such as a web browser.
|
|
405
387
|
|
|
406
|
-
Three different "
|
|
388
|
+
Three different "environment" implementations are shipped with this package:
|
|
407
389
|
|
|
408
|
-
- Use `
|
|
409
|
-
- Use `
|
|
410
|
-
- Use `
|
|
390
|
+
- Use `WebBrowserEnvironment` in a web browser. Navigation session survives a page refresh and is only destroyed when the web browser tab gets closed. Create a single `NavigationStack` instance per web browser tab.
|
|
391
|
+
- Use `ServerSideRenderEnvironment` in server-side rendering. Create a separate `NavigationStack` instance for each incoming HTTP request. Initialize it with a relative URL of the HTTP request. If, during server-side render, the application code attempts to navigate to another location, it will throw a `ServerSideRedirectError` with a `location` property in it.
|
|
392
|
+
- Use `InMemoryEnvironment` in tests to mimick a `WebBrowserEnvironment`. One can create as many separate `NavigationStack` instances as required because they're completely independent/isolated from one another. Initialize it with a relative URL or a location object.
|
|
411
393
|
|
|
412
394
|
<details>
|
|
413
|
-
<summary>See <code>
|
|
395
|
+
<summary>See <code>ServerSideRenderEnvironment</code> example</summary>
|
|
414
396
|
|
|
415
397
|
######
|
|
416
398
|
|
|
417
399
|
```js
|
|
418
|
-
const navigationStack = new NavigationStack(
|
|
400
|
+
const navigationStack = new NavigationStack(ServerSideRenderEnvironment)
|
|
419
401
|
|
|
420
402
|
navigationStack.subscribe((location) => {
|
|
421
403
|
console.log('Current location', location)
|
|
@@ -426,19 +408,19 @@ navigationStack.subscribe((location) => {
|
|
|
426
408
|
navigationStack.init('/initial-location')
|
|
427
409
|
|
|
428
410
|
// Navigates to a new location.
|
|
429
|
-
// Throws `
|
|
411
|
+
// Throws `ServerSideRedirectError` with a `location` property.
|
|
430
412
|
navigationStack.push('/new-location')
|
|
431
413
|
```
|
|
432
414
|
</details>
|
|
433
415
|
|
|
434
416
|
|
|
435
417
|
<details>
|
|
436
|
-
<summary>See <code>
|
|
418
|
+
<summary>See <code>InMemoryEnvironment</code> example</summary>
|
|
437
419
|
|
|
438
420
|
######
|
|
439
421
|
|
|
440
422
|
```js
|
|
441
|
-
const navigationStack = new NavigationStack(
|
|
423
|
+
const navigationStack = new NavigationStack(InMemoryEnvironment)
|
|
442
424
|
|
|
443
425
|
navigationStack.subscribe((location) => {
|
|
444
426
|
console.log('Current location', location)
|
|
@@ -456,8 +438,7 @@ navigationStack.push('/new-location')
|
|
|
456
438
|
|
|
457
439
|
######
|
|
458
440
|
|
|
459
|
-
|
|
460
|
-
|
|
441
|
+
<!--
|
|
461
442
|
Once created, a "session" is simply passed to the `NavigationStack` constructor and then you don't have to deal with it anymore — `NavigationStack` will pull all the strings for you.
|
|
462
443
|
|
|
463
444
|
However, if someone prefers to completely bypass `NavigationStack` and interact with a "session" object directly, they could do so.
|
|
@@ -481,12 +462,13 @@ However, if someone prefers to completely bypass `NavigationStack` and interact
|
|
|
481
462
|
* `delta: number` after a `.shift(delta)` navigation, i.e. "back or forward navigation".
|
|
482
463
|
* `-1` after the user clicks a "Back" button in their web browser.
|
|
483
464
|
* `1` after the user clicks a "Forward" button in their web browser.
|
|
484
|
-
|
|
465
|
+
<!- * `getInitialLocation(): object?` — Returns the initial location, if the session can get it from somewhere. For example, in a web browser, the initial location can be read from `window.location`. In other environments, such as server side, the initial location can't be read from anywhere. ->
|
|
485
466
|
* `start(initialLocation?: object)` — Starts the session. The `initialLocation` argument is optional when the session can read it from somewhere. For example, `WebBrowserSession` can read `initialLocation` from `window.location`.
|
|
486
467
|
* `stop()` — Stops the session. Cleans up any listeners, etc.
|
|
487
468
|
* `navigate(operation: string, location: object)` — Navigates to a `location` using either `"PUSH"` or `"REPLACE"` operation. The `location` argument should be a result of calling `parseInputLocation()` function.
|
|
488
469
|
* `shift(delta: number)` — Navigates "back" or "forward" by skipping a specified count of pages. Negative `delta` skips backwards, positive `delta` skips forward.
|
|
489
470
|
</details>
|
|
471
|
+
-->
|
|
490
472
|
|
|
491
473
|
## Utility
|
|
492
474
|
|
|
@@ -549,25 +531,19 @@ removeBasePath({ pathname: '/base-path/abc' }, '/base-path') === { pathname: '/a
|
|
|
549
531
|
|
|
550
532
|
## Block Navigation
|
|
551
533
|
|
|
552
|
-
`
|
|
534
|
+
`NavigationStack` provides the ability to block navigation. Call `.addNavigationBlocker()` method to set up a "navigation blocker".
|
|
553
535
|
|
|
554
536
|
```js
|
|
555
537
|
import {
|
|
556
538
|
NavigationStack,
|
|
557
|
-
|
|
558
|
-
addNavigationBlocker
|
|
539
|
+
WebBrowserEnvironment
|
|
559
540
|
} from 'navigation-stack'
|
|
560
541
|
|
|
561
|
-
// Create a session.
|
|
562
|
-
const session = new WebBrowserSession()
|
|
563
|
-
|
|
564
542
|
// Create a `NavigationStack` instance.
|
|
565
|
-
const navigationStack = new NavigationStack(
|
|
543
|
+
const navigationStack = new NavigationStack(WebBrowserEnvironment)
|
|
566
544
|
|
|
567
545
|
// Add a navigation blocker.
|
|
568
|
-
|
|
569
|
-
const removeNavigationBlocker = addNavigationBlocker(
|
|
570
|
-
session,
|
|
546
|
+
const removeNavigationBlocker = navigationStack.addNavigationBlocker(
|
|
571
547
|
(newLocation) => {
|
|
572
548
|
// Returning `true` means "this navigation should be blocked".
|
|
573
549
|
return true
|
|
@@ -594,188 +570,74 @@ navigationStack.push('/new-location')
|
|
|
594
570
|
|
|
595
571
|
Navigation blocker should be a function that receives a `newLocation` argument and could be "synchronous" or "asynchronous" (i.e. return a `Promise`, aka `async`/`await`).
|
|
596
572
|
|
|
597
|
-
The `newLocation` argument of a blocker function
|
|
573
|
+
The `newLocation` argument of a blocker function is an object that has all the properties of a [standard web browser location](https://developer.mozilla.org/en-US/docs/Web/API/Window/location) with the addition of a `query` object.
|
|
598
574
|
|
|
599
575
|
Navigation blockers fire both when navigating from one page to another and when closing the current browser tab. In the latter case, `newLocation` argument will be `null`, and also the blocker function can't return a `Promise` (because it won't wait), and returning `true` from it will cause the web browser will to show a confirmation modal with a non-customizable generic browser-specific text like "Leave site? Changes you made might not be saved".
|
|
600
576
|
|
|
601
577
|
## Data Storage
|
|
602
578
|
|
|
603
|
-
One could use `
|
|
579
|
+
One could use `NavigationStack`'s "data storage" to store any kind of application-specific data within the bounds of a given "session", which could be defined as the time from "opening" the application to "closing" it. As long as the "session" exists, so does the data in the "data storage".
|
|
604
580
|
|
|
605
|
-
|
|
581
|
+
For example, in a web browser environment, a "session" starts when the user opens a website in a web browser window or tab, and ends when the user closes that web browser window or tab, and such "session" also survives a "page refresh".
|
|
606
582
|
|
|
607
|
-
|
|
583
|
+
Different types of data could be stored under a different `key`.
|
|
608
584
|
|
|
609
|
-
|
|
585
|
+
Each different location has it's own isolated data storage compartment, so the same `key` could be reused by different locations and there'd be no conflict. For example, one could store scroll position for each different page under `key: "scroll-position"` to be able to restore it when the user decides to navigate "Back" to that page. By the way, that's how `manageScrollPosition: true` feature works.
|
|
610
586
|
|
|
611
587
|
```js
|
|
612
|
-
import {
|
|
613
|
-
import { DataStorage, LocationDataStorage } from 'navigation-stack/data-storage'
|
|
614
|
-
|
|
615
|
-
const session = new WebBrowserSession()
|
|
616
|
-
|
|
617
|
-
// `DataStorage` example
|
|
588
|
+
import { NavigationStack, WebBrowserEnvironment } from 'navigation-stack'
|
|
618
589
|
|
|
619
|
-
const
|
|
590
|
+
const navigationStack = new NavigationStack(WebBrowserEnvironment)
|
|
620
591
|
|
|
621
|
-
|
|
622
|
-
dataStorage.get('key') === 123
|
|
623
|
-
|
|
624
|
-
// `LocationDataStorage` example
|
|
625
|
-
|
|
626
|
-
const locationDataStorage = new LocationDataStorage(session, { namespace: 'my-namespace' })
|
|
592
|
+
navigationStack.init()
|
|
627
593
|
|
|
628
|
-
const location =
|
|
594
|
+
const location = navigationStack.current()
|
|
629
595
|
|
|
630
|
-
|
|
631
|
-
|
|
596
|
+
navigationStack.dataStorage.set(location, 'key', 123)
|
|
597
|
+
navigationStack.dataStorage.get(location, 'key') === 123
|
|
632
598
|
```
|
|
633
599
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
One might ask: Why use `DataStorage` or `LocationDataStorage` when one could simply store the data in a usual variable? The answer is that a usual variable doesn't survive if the user decides to refresh the page. But the entire navigation history does survive because that's how web browsers work. So if the user decides to go "Back" after refreshing the current page, the data associated to that previous location would already be lost and can't be recovered. In contrast, when using a `DataStorage` or `LocationDataStorage` with a `WebBrowserSession`, the stored data does survive a page refresh, which feels more consistent and coherent with the persistence behavior of the navigation history itself.
|
|
637
|
-
|
|
638
|
-
## Redux
|
|
639
|
-
|
|
640
|
-
Under the hood, `navigation-stack` uses [`redux`](https://redux.js.org/). Why? For no particular reason. The original [`farce`](http://npmjs.com/package/farce) package was published in September 2016, and by that time `redux` had still been a hot topic since [July 2025](https://www.youtube.com/watch?v=xsSnOQynTHs). This package could most certainly be rewritten without using `redux`, it's just that there seems to be no need to do that.
|
|
641
|
-
|
|
642
|
-
So since `navigation-stack` already implements all that `redux` stuff internally, such as "middlewares" or "actions", why not export it for public usage? Maybe there're still some `redux` fans out there.
|
|
643
|
-
|
|
644
|
-
Using `navigation-stack` `redux`-way is equivalent to using it the conventional way via `NavigationStack` class. `navigation-stack` exports "middlewares", "actions" and a "reducer" that could be used in conjunction with `redux` or any other `redux`-compatible package (e.g. [`mini-redux`](https://www.npmjs.com/package/mini-redux)).
|
|
600
|
+
The data storage doesn't provide strict guarantees about actually storing the data: if it encounters an unexpected storage error in the process, it will simply ignore it. This simplifies the API in a way that the application doesn't have to wrap `.get()`/`.set()` calls in a `try/catch` block. And judging by the nature of location-specific data, that type of data is inherently non-essential (non-critical) and rather "nice-to-have".
|
|
645
601
|
|
|
646
602
|
<details>
|
|
647
|
-
<summary>
|
|
603
|
+
<summary>Examples of ignored errors in a <code>WebBrowserEnvironment</code>.</summary>
|
|
648
604
|
|
|
649
605
|
######
|
|
650
606
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
```js
|
|
654
|
-
import { createStore, applyMiddleware } from 'redux';
|
|
655
|
-
|
|
656
|
-
import {
|
|
657
|
-
createMiddlewares,
|
|
658
|
-
locationReducer,
|
|
659
|
-
Actions,
|
|
660
|
-
WebBrowserSession,
|
|
661
|
-
} from 'navigation-stack';
|
|
662
|
-
|
|
663
|
-
// Create a Redux store.
|
|
664
|
-
const store = createStore(
|
|
665
|
-
// Reducer function. For example, `locationReducer()`.
|
|
666
|
-
locationReducer,
|
|
667
|
-
// It should be tied to a navigation "session".
|
|
668
|
-
applyMiddleware(...createMiddlewares(new WebBrowserSession())),
|
|
669
|
-
);
|
|
670
|
-
```
|
|
607
|
+
* `SecurityError` — In "Private"/"Incognito" browsing mode, many browsers block write access to storage APIs to enhance privacy. Attempting to call `sessionStorage.setItem()` will throw a `SecurityError` in such case. Same error could be a result of using a really strict privacy blocker extension or opening the website from an `*.html` file directly from disk (`file://` URL).
|
|
671
608
|
|
|
672
|
-
|
|
609
|
+
* `QuotaExceededError` — Could happen if the application attempts to store too much data in `navigation-stack`'s "data storage", or if the application has already used up all available space in `sessionStorage` for some other purposes. The maximum available space in `sessionStorage` depends on the web browser and is usually assumed to be around `5 MB` per URL origin.
|
|
610
|
+
</details>
|
|
673
611
|
|
|
674
|
-
|
|
675
|
-
// Sets the initial `location`.
|
|
676
|
-
//
|
|
677
|
-
// Accepts either a relative URL string or a location object.
|
|
678
|
-
//
|
|
679
|
-
// The initial location argument could be omitted for `WebBrowserSession`
|
|
680
|
-
// because it can read it by itself from `window.location`.
|
|
681
|
-
// Other types of session such as `InMemorySession` or `ServerSideRenderSession`
|
|
682
|
-
// don't have an initial location and require the initial location argument
|
|
683
|
-
// to be specified explicitly when creating an `Actions.init(initialLocation)` action.
|
|
684
|
-
//
|
|
685
|
-
store.dispatch(Actions.init());
|
|
686
|
-
```
|
|
612
|
+
######
|
|
687
613
|
|
|
688
|
-
|
|
614
|
+
One might ask: Why use `NavigationStack`'s data storage when one could simply store the data in a usual variable? The answer is that a usual variable doesn't survive if the user decides to refresh the page. But the entire navigation history does survive because that's how web browsers work. So if the user decides to go "Back" after refreshing the current page, the data associated to that previous location would already be lost and can't be recovered. In contrast, when using `NavigationStack` with a `WebBrowserEnvironment`, the stored data does survive a page refresh, which feels more consistent and coherent with the persistence behavior of the navigation history itself.
|
|
689
615
|
|
|
690
|
-
|
|
691
|
-
let currentLocation;
|
|
616
|
+
## Development
|
|
692
617
|
|
|
693
|
-
|
|
694
|
-
const store = createStore(
|
|
695
|
-
locationReducer, // Reducer function. For example, `locationReducer()`.
|
|
696
|
-
applyMiddleware(...createMiddlewares(new WebBrowserSession())),
|
|
697
|
-
);
|
|
618
|
+
Clone the repository. Then:
|
|
698
619
|
|
|
699
|
-
// Subscribe to any potential Redux state changes.
|
|
700
|
-
const unsubscribe = store.subscribe(() => {
|
|
701
|
-
const previousLocation = currentLocation;
|
|
702
|
-
currentLocation = store.getState(); // In case of using `locationReducer()`.
|
|
703
|
-
if (currentLocation !== previousLocation) {
|
|
704
|
-
console.log('Current location', currentLocation);
|
|
705
|
-
}
|
|
706
|
-
});
|
|
707
620
|
```
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
```js
|
|
712
|
-
// Sets the `location` to be a new location.
|
|
713
|
-
//
|
|
714
|
-
// Also updates the URL in the web browser's address bar.
|
|
715
|
-
//
|
|
716
|
-
// Also adds a new entry in the web browser's navigation history.
|
|
717
|
-
//
|
|
718
|
-
store.dispatch(Actions.push('/new-location'));
|
|
719
|
-
|
|
720
|
-
// Sets the `location` to be a new location.
|
|
721
|
-
//
|
|
722
|
-
// Also updates the URL in the web browser's address bar.
|
|
723
|
-
//
|
|
724
|
-
// Does not add a new entry in the web browser's navigation history
|
|
725
|
-
// which is the only difference between this and `Actions.push()`.
|
|
726
|
-
//
|
|
727
|
-
store.dispatch(Actions.replace('/new-location'));
|
|
728
|
-
|
|
729
|
-
// Sets the `location` to be a previous one (if there is one).
|
|
730
|
-
// One could think of it as an equivalent of clicking a "Back" button in a web browser.
|
|
731
|
-
//
|
|
732
|
-
// Also updates the URL in the web browser's address bar.
|
|
733
|
-
//
|
|
734
|
-
// Also shifts the current position in the web browser's navigation history.
|
|
735
|
-
//
|
|
736
|
-
store.dispatch(Actions.shift(-1));
|
|
737
|
-
|
|
738
|
-
// Sets the `location` to be a next one (if there is one).
|
|
739
|
-
// One could think of it as an equivalent of clicking a "Forward" button in a web browser.
|
|
740
|
-
//
|
|
741
|
-
// Also updates the URL in the web browser's address bar.
|
|
742
|
-
//
|
|
743
|
-
// Also shifts the current position in the web browser's navigation history.
|
|
744
|
-
//
|
|
745
|
-
store.dispatch(Actions.shift(1));
|
|
621
|
+
yarn
|
|
622
|
+
yarn format
|
|
623
|
+
yarn test
|
|
746
624
|
```
|
|
747
625
|
|
|
748
|
-
|
|
626
|
+
It will open two web browser windows — Firefox and Chrome — and run live tests in those. The web browsers are specified in `karma.conf.cjs` file. When running tests, don't unfocus the web browser windows, otherwise the tests will fail with random errors. If you're not unfocusing the web browser windows and the tests still fail with random errors, see if increasing the interval in `await delay(100)` calls in tests fixes the issue.
|
|
749
627
|
|
|
750
|
-
|
|
751
|
-
// When `locationReducer()` is used, `store.getState()` returns the current location.
|
|
752
|
-
const location = store.getState();
|
|
753
|
-
console.log(location);
|
|
754
|
-
```
|
|
628
|
+
## Development History
|
|
755
629
|
|
|
756
|
-
|
|
630
|
+
Originally it started from a fork of [`farce`](http://npmjs.com/package/farce) package to fix a couple of small bugs there ([1](https://github.com/4Catalyzer/farce/issues/483), [2](https://github.com/4Catalyzer/farce/issues/491)).
|
|
757
631
|
|
|
758
|
-
|
|
759
|
-
// (optional)
|
|
760
|
-
// When the user closes the application,
|
|
761
|
-
// stop the session and clean up any listeners.
|
|
762
|
-
// There's no need to do this in a web browser.
|
|
763
|
-
unsubscribe();
|
|
764
|
-
store.dispatch(Actions.stop());
|
|
765
|
-
```
|
|
766
|
-
</details>
|
|
632
|
+
Then I decided to merge it with [`scroll-behavior`](http://npmjs.com/package/scroll-behavior) package to fix a couple of small bugs there ([1](https://github.com/taion/scroll-behavior/issues/215), [2](https://github.com/taion/scroll-behavior/pull/472)).
|
|
767
633
|
|
|
768
|
-
|
|
634
|
+
Then I decided to completely reorganize and refactor the entire code.
|
|
769
635
|
|
|
770
|
-
|
|
636
|
+
Then I decided to remove Redux and expose a more conventional and simple API. The original [`farce`](http://npmjs.com/package/farce) package was published in September 2016, and by that time `redux` had [still been](https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367) a hot topic since [July 2015](https://www.youtube.com/watch?v=xsSnOQynTHs). But in retrospect, there were no legitimate reasons to heavily rely on Redux when implementing such a small and universal library. Apparently, in 2016, everyone went crazy over Redux and it became a de-facto standard when building just about any React web application, to the point of assuming that if you're building a React web app, you're 100% building it on Redux, so all hot frameworks should reuse that Redux for both the internal implementation and the public API.
|
|
771
637
|
|
|
772
|
-
|
|
773
|
-
yarn
|
|
774
|
-
yarn format
|
|
775
|
-
yarn test
|
|
776
|
-
```
|
|
638
|
+
> Redux was a revolutionary technology in the React ecosystem. It enabled us to have a global store with [immutable data](https://medium.com/dailyjs/the-state-of-immutability-169d2cd11310) and **fixed the issue of [prop-drilling](https://kentcdodds.com/blog/prop-drilling) in our component tree. For sharing immutable data across an application, it continues to be an excellent tool that scales really well.
|
|
777
639
|
|
|
778
|
-
|
|
640
|
+
Source: [Why I Stopped Using Redux](https://dev.to/g_abud/why-i-quit-redux-1knl)
|
|
779
641
|
|
|
780
642
|
## GitHub
|
|
781
643
|
|