Skip to content

Commit c8a86ff

Browse files
committed
Add createScrollBehavior hook
1 parent fb8d995 commit c8a86ff

File tree

5 files changed

+104
-4
lines changed

5 files changed

+104
-4
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ useScroll((prevRouterProps, { routes }) => {
6666
});
6767
```
6868

69+
You can customize `useScroll` even further by providing a configuration object with a `createScrollBehavior` callback that creates the scroll behavior object. This allows using a custom subclass of `ScrollBehavior` from scroll-behavior with custom logic. When using a configuration object, you can specify the `shouldUpdateScroll` callback as above under the `shouldUpdateScroll` key.
70+
71+
```js
72+
useScroll({
73+
createScrollBehavior: (config) => new MyScrollBehavior(config),
74+
shouldUpdateScroll,
75+
});
76+
```
77+
6978
### Scrolling elements other than `window`
7079

7180
Use `<ScrollContainer>` in components rendered by a router with the `useScroll` middleware to manage the scroll behavior of elements other than `window`. Each `<ScrollContainer>` must be given a unique `scrollKey`, and can be given an optional `shouldUpdateScroll` callback that behaves as above.

src/ScrollBehaviorContext.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import PropTypes from 'prop-types';
22
import React from 'react';
3-
import ScrollBehavior from 'scroll-behavior';
43

54
import StateStorage from './StateStorage';
65

76
const propTypes = {
87
shouldUpdateScroll: PropTypes.func,
8+
createScrollBehavior: PropTypes.func.isRequired,
99
routerProps: PropTypes.object.isRequired,
1010
children: PropTypes.element.isRequired,
1111
};
@@ -21,7 +21,7 @@ class ScrollBehaviorContext extends React.Component {
2121
const { routerProps } = props;
2222
const { router } = routerProps;
2323

24-
this.scrollBehavior = new ScrollBehavior({
24+
this.scrollBehavior = props.createScrollBehavior({
2525
addTransitionHook: router.listenBefore,
2626
stateStorage: new StateStorage(router),
2727
getCurrentLocation: () => this.props.routerProps.location,

src/useScroll.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,34 @@
11
import React from 'react';
2+
import ScrollBehavior from 'scroll-behavior';
23

34
import ScrollBehaviorContext from './ScrollBehaviorContext';
45

5-
export default function useScroll(shouldUpdateScroll) {
6+
function defaultCreateScrollBehavior(config) {
7+
return new ScrollBehavior(config);
8+
}
9+
10+
export default function useScroll(shouldUpdateScrollOrConfig) {
11+
let shouldUpdateScroll;
12+
let createScrollBehavior;
13+
14+
if (
15+
!shouldUpdateScrollOrConfig ||
16+
typeof shouldUpdateScrollOrConfig === 'function'
17+
) {
18+
shouldUpdateScroll = shouldUpdateScrollOrConfig;
19+
createScrollBehavior = defaultCreateScrollBehavior;
20+
} else {
21+
({
22+
shouldUpdateScroll,
23+
createScrollBehavior = defaultCreateScrollBehavior,
24+
} = shouldUpdateScrollOrConfig);
25+
}
26+
627
return {
728
renderRouterContext: (child, props) => (
829
<ScrollBehaviorContext
930
shouldUpdateScroll={shouldUpdateScroll}
31+
createScrollBehavior={createScrollBehavior}
1032
routerProps={props}
1133
>
1234
{child}

test/run.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export function delay(cb) {
22
// Give throttled scroll listeners time to settle down.
3-
setTimeout(cb, 80);
3+
setTimeout(cb, 60);
44
}
55

66
export default function run(steps) {

test/useScroll.test.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import createHashHistory from 'history/lib/createHashHistory';
55
import React from 'react';
66
import ReactDOM from 'react-dom';
77
import { applyRouterMiddleware, Router, useRouterHistory } from 'react-router';
8+
import ScrollBehavior from 'scroll-behavior';
89

910
import StateStorage from '../src/StateStorage';
1011
import useScroll from '../src/useScroll';
@@ -144,6 +145,74 @@ describe('useScroll', () => {
144145
container,
145146
);
146147
});
148+
149+
it('should support a custom scroll behavior factory', (done) => {
150+
class MyScrollBehavior extends ScrollBehavior {
151+
scrollToTarget() {
152+
window.scrollTo(0, 50);
153+
}
154+
}
155+
156+
const steps = [
157+
() => {
158+
history.push('/page2');
159+
},
160+
() => {
161+
expect(scrollTop(window)).to.equal(50);
162+
163+
done();
164+
},
165+
];
166+
167+
ReactDOM.render(
168+
<Router
169+
history={history}
170+
routes={routes}
171+
render={applyRouterMiddleware(useScroll({
172+
createScrollBehavior: config => new MyScrollBehavior(config),
173+
}))}
174+
onUpdate={run(steps)}
175+
/>,
176+
container,
177+
);
178+
});
179+
180+
it('should support fully custom behavior', (done) => {
181+
class MyScrollBehavior extends ScrollBehavior {
182+
scrollToTarget(element, target) {
183+
element.scrollTo(20, target[1] + 10);
184+
}
185+
}
186+
187+
function shouldUpdateScroll() {
188+
return [0, 50];
189+
}
190+
191+
const steps = [
192+
() => {
193+
history.push('/page2');
194+
},
195+
() => {
196+
expect(scrollLeft(window)).to.equal(20);
197+
expect(scrollTop(window)).to.equal(60);
198+
199+
done();
200+
},
201+
];
202+
203+
ReactDOM.render(
204+
<Router
205+
history={history}
206+
routes={routes}
207+
render={applyRouterMiddleware(useScroll({
208+
createScrollBehavior: config => new MyScrollBehavior(config),
209+
shouldUpdateScroll,
210+
}))}
211+
onUpdate={run(steps)}
212+
/>,
213+
container,
214+
);
215+
});
147216
});
148217
});
149218
});

0 commit comments

Comments
 (0)