From 56166a80cb562ffc1066f25e7f88a2d6d3ecc32a Mon Sep 17 00:00:00 2001 From: James Gillmore Date: Sun, 7 May 2017 01:52:28 -0700 Subject: [PATCH] feat($scrollRestoration): add feature to disable automatic scroll restoration via manual: true optio --- .eslintrc.js | 1 + README.md | 2 +- src/action-creators/middlewareCreateAction.js | 68 +++++++++---------- src/connectRoutes.js | 36 +++++----- 4 files changed, 52 insertions(+), 55 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 9a709f9c..216a5854 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -55,6 +55,7 @@ module.exports = { 'no-unused-vars': 1, 'import/no-unresolved': 1, 'flowtype/no-weak-types': 1, + 'consistent-return': 1, semi: [2, 'never'], 'no-console': [2, { allow: ['warn', 'error'] }], 'flowtype/semi': [2, 'never'], diff --git a/README.md b/README.md index 163db089..6e2de0ed 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,7 @@ and you're free to make your own. Basically it passes the `href` on to **Redux F into a URL for you! The package is obvious enough once you get the hang of what's going on here--check it out when you're ready: [redux-first-router-link](http://github.com/faceyspacey/redux-first-router-link). And if you're wondering, we don't offer route matching components like *React Router*--that's what state is for! -See our FAQ below. +See our [FAQ](#faq) below. ## routesMap diff --git a/src/action-creators/middlewareCreateAction.js b/src/action-creators/middlewareCreateAction.js index eaf89f08..8c50b05d 100644 --- a/src/action-creators/middlewareCreateAction.js +++ b/src/action-creators/middlewareCreateAction.js @@ -8,11 +8,11 @@ export default ( action: Object, routesMap: RoutesMap, prevLocation: Location, - history: History + hist: History ): Action => { try { const pathname = actionToPath(action, routesMap) - const kind = getKind(pathname, history) + const [kind, history] = getKindAndHistory(!!hist.entries, pathname, hist) return nestAction(pathname, action, prevLocation, history, kind) } catch (e) { @@ -25,49 +25,45 @@ export default ( pathname, { type: NOT_FOUND, payload }, prevLocation, - history + hist ) } } -const getKind = (pathname: string, history: History): ?string => { - const isMemoryHistory = !!history.entries - +// REACT NATIVE FEATURE: +// emulate npm `history` package and `historyCreateAction` so that actions +// and state indicate the user went back or forward. The idea is if you are +// going back or forward to a route you were just at, apps can determine +// from `state.location.backNext` and `action.backNext` that things like +// scroll position should be restored. +// NOTE: for testability, history is also returned to make this a pure function +const getKindAndHistory = ( + isMemoryHistory: boolean, + pathname: string, + history: History +): [?string, History] => { if (!isMemoryHistory) { - return undefined + return [undefined, history] } - const isLast = history.index === history.length - 1 - const prev = history.entries[history.index - 1] - // REACT NATIVE FEATURE: - // emulate npm `history` package and `historyCreateAction` so that actions - // and state indicate the user went back or forward. The idea is if you are - // going back or forward to a route you were just at, apps can determine - // from `state.location.backNext` and `action.backNext` that things like - // scroll position should be restored. - if (isLast && prev) { - const prevPath = prev.pathname - const isGoingBack = prevPath === pathname - - if (isGoingBack) { - history.index-- - return 'backNext' - } - - return undefined + if (goingBack(pathname, history)) { + history.index-- + return ['backNext', history] + } + else if (goingForward(pathname, history)) { + history.index++ + return ['backNext', history] } - const next = history.entries[history.index + 1] - - if (next) { - const nextPath = next.pathname - const isGoingForward = nextPath === pathname + return [undefined, history] +} - if (isGoingForward) { - history.index++ - return 'backNext' - } - } +const goingBack = (pathname: string, history: History): boolean => { + const prev = history.entries[history.index - 1] + return prev && prev.pathname === pathname +} - return undefined +const goingForward = (pathname: string, history: History): boolean => { + const next = history.entries[history.index + 1] + return next && next.pathname === pathname } diff --git a/src/connectRoutes.js b/src/connectRoutes.js index 53663adf..e314aeb3 100644 --- a/src/connectRoutes.js +++ b/src/connectRoutes.js @@ -277,22 +277,8 @@ export default ( history.listen(_historyAttemptDispatchAction.bind(null, store)) - _updateScroll = () => { - if (scrollBehavior) { - scrollBehavior.updateScroll(prevState, nextState) - } - else if (process.env.NODE_ENV !== 'production') { - throw new Error( - `[redux-first-router] you must set the \`restoreScroll\` option before - you can call \`updateScroll\`` - ) - } - } - // update the scroll position after initial rendering of page - if (scrollBehavior) { - setTimeout(_updateScroll, 0) - } + setTimeout(() => _updateScroll(false)) // dispatch the first location-aware action so initial app state is based on the url on load if (!location.hasSSR || isServer()) { @@ -337,14 +323,28 @@ export default ( } } - if (scrollBehavior) { - _updateScroll() - } + _updateScroll(false) } + /* SIDE_EFFECTS - client-only state that must escape closure */ + _history = history _scrollBehavior = scrollBehavior + _updateScroll = (performedByUser: boolean = true) => { + if (scrollBehavior) { + if (!scrollBehavior.manual) { + scrollBehavior.updateScroll(prevState, nextState) + } + } + else if (process.env.NODE_ENV !== 'production' && performedByUser) { + throw new Error( + `[redux-first-router] you must set the \`restoreScroll\` option before + you can call \`updateScroll\`` + ) + } + } + /* RETURN */ return {