Skip to content
This repository was archived by the owner on Apr 9, 2019. It is now read-only.

Commit ae1359b

Browse files
committed
Merge pull request #3 from programmarchy/lifecycle-events
Add lifecycle events for hiding / showing views before / after nav transitions
2 parents 6d8e00a + 99e1453 commit ae1359b

File tree

3 files changed

+175
-6
lines changed

3 files changed

+175
-6
lines changed

README.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
React view manager similar to [UINavigationController][ios-controller]
77

8-
## Instalation
8+
## Installation
99

1010
```bash
1111
npm install react-navigation-controller
@@ -180,6 +180,46 @@ If set to `true`, the navigationController will save the state
180180
of each view that gets pushed onto the stack. When `popView()` is called,
181181
the navigationController will rehydrate the state of the view before it is shown.
182182

183+
## Lifecycle Events
184+
185+
Similar to the React component lifecycle, the navigationController will
186+
call lifecycle events on the component at certain stages.
187+
188+
Lifecycle events can trigger actions when views transition in or out,
189+
instead of mounted or unmounted:
190+
191+
```
192+
class HelloView extends React.Component {
193+
navigationControllerDidShowView() {
194+
// Do something when the show transition is finished,
195+
// like fade in an element.
196+
}
197+
navigationControllerWillHideView() {
198+
// Do something when the hide transition will start,
199+
// like fade out an element.
200+
}
201+
render() {
202+
return <div>Hello, {this.props.name}!</div>;
203+
}
204+
}
205+
```
206+
207+
### `view.navigationControllerWillHideView()`
208+
209+
Invoked immediately before the previous view will be hidden.
210+
211+
### `view.navigationControllerWillShowView()`
212+
213+
Invoked immediately before the next view will be shown.
214+
215+
### `view.navigationControllerDidHideView()`
216+
217+
Invoked immediately after the previous view has been hidden.
218+
219+
### `view.navigationControllerDidShowView()`
220+
221+
Invoked immediately after the next view has been shown.
222+
183223
## Styling
184224

185225
No default styles are provided, but classes are added for custom styling:

spec/navigation-controller.spec.jsx

Lines changed: 112 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ describe('NavigationController', () => {
3232
);
3333
viewWrapper0 = controller['__view-wrapper-0'];
3434
viewWrapper1 = controller['__view-wrapper-1'];
35-
})
35+
});
3636
it('exports a component', () => {
3737
let controller = renderIntoDocument(
3838
<NavigationController views={views} />
@@ -85,9 +85,6 @@ describe('NavigationController', () => {
8585
expect(viewWrapper0).to.have.deep.property(`style.${transformPrefix}`);
8686
expect(viewWrapper1).to.have.deep.property(`style.${transformPrefix}`);
8787
});
88-
});
89-
describe('#componentWillUnmout', () => {
90-
9188
});
9289
describe('#__transformViews', () => {
9390
beforeEach(done => {
@@ -668,4 +665,115 @@ describe('NavigationController', () => {
668665
});
669666
});
670667
});
668+
describe('Lifecycle Events', () => {
669+
let stubLifecycleEvents = (onTransitionViews) => {
670+
const e = {
671+
prevView: {
672+
willHide: sinon.spy(),
673+
didHide: sinon.spy()
674+
},
675+
nextView: {
676+
willShow: sinon.spy(),
677+
didShow: sinon.spy()
678+
}
679+
}
680+
const stub = sinon.stub(controller, '__transitionViews', (options) => {
681+
let prevView = controller.refs['view-0'];
682+
if (prevView) {
683+
prevView.navigationControllerWillHideView = e.prevView.willHide;
684+
prevView.navigationControllerDidHideView = e.prevView.didHide;
685+
}
686+
let nextView = controller.refs['view-1'];
687+
if (nextView) {
688+
nextView.navigationControllerWillShowView = e.nextView.willShow;
689+
nextView.navigationControllerDidShowView = e.nextView.didShow;
690+
}
691+
stub.restore();
692+
controller.__transitionViews(options);
693+
onTransitionViews();
694+
});
695+
return e;
696+
};
697+
describe('#__pushView', () => {
698+
beforeEach(done => {
699+
requestAnimationFrame(() => {
700+
done();
701+
});
702+
});
703+
it('calls events with a "none" transition', (done) => {
704+
const e = stubLifecycleEvents(() => {
705+
expect(e.prevView.willHide.calledOnce).to.be.true;
706+
expect(e.nextView.willShow.calledOnce).to.be.true;
707+
expect(e.prevView.didHide.calledOnce).to.be.false;
708+
expect(e.nextView.didShow.calledOnce).to.be.false;
709+
});
710+
controller.__pushView(<ViewB />, {
711+
transition: Transition.type.NONE,
712+
onComplete() {
713+
expect(e.prevView.didHide.calledOnce).to.be.true;
714+
expect(e.nextView.didShow.calledOnce).to.be.true;
715+
done();
716+
}
717+
});
718+
});
719+
it('calls events with a built-in spring animation', (done) => {
720+
const e = stubLifecycleEvents(() => {
721+
expect(e.prevView.willHide.calledOnce).to.be.true;
722+
expect(e.nextView.willShow.calledOnce).to.be.true;
723+
expect(e.prevView.didHide.calledOnce).to.be.false;
724+
expect(e.nextView.didShow.calledOnce).to.be.false;
725+
});
726+
controller.__pushView(<ViewB />, {
727+
transition: Transition.type.PUSH_LEFT,
728+
onComplete() {
729+
expect(e.prevView.didHide.calledOnce).to.be.true;
730+
expect(e.nextView.didShow.calledOnce).to.be.true;
731+
done();
732+
}
733+
});
734+
});
735+
});
736+
describe('#__popView', () => {
737+
beforeEach(done => {
738+
controller = renderIntoDocument(
739+
<NavigationController views={[<ViewA />,<ViewB />]} />
740+
);
741+
requestAnimationFrame(() => {
742+
done();
743+
});
744+
});
745+
it('calls events with a "none" transition', (done) => {
746+
const e = stubLifecycleEvents(() => {
747+
expect(e.prevView.willHide.calledOnce).to.be.true;
748+
expect(e.nextView.willShow.calledOnce).to.be.true;
749+
expect(e.prevView.didHide.calledOnce).to.be.false;
750+
expect(e.nextView.didShow.calledOnce).to.be.false;
751+
});
752+
controller.__popView({
753+
transition: Transition.type.NONE,
754+
onComplete() {
755+
expect(e.prevView.didHide.calledOnce).to.be.true;
756+
expect(e.nextView.didShow.calledOnce).to.be.true;
757+
done();
758+
}
759+
});
760+
});
761+
it('calls events with a built-in spring animation', (done) => {
762+
const e = stubLifecycleEvents(() => {
763+
expect(e.prevView.willHide.calledOnce).to.be.true;
764+
expect(e.nextView.willShow.calledOnce).to.be.true;
765+
expect(e.prevView.didHide.calledOnce).to.be.false;
766+
expect(e.nextView.didShow.calledOnce).to.be.false;
767+
});
768+
controller.__popView({
769+
transition: Transition.type.PUSH_LEFT,
770+
onComplete() {
771+
expect(e.prevView.didHide.calledOnce).to.be.true;
772+
expect(e.nextView.didShow.calledOnce).to.be.true;
773+
done();
774+
}
775+
});
776+
});
777+
});
778+
});
671779
});

src/navigation-controller.jsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,21 @@ class NavigationController extends React.Component {
206206
// Hide the previous view wrapper
207207
const prevViewWrapper = this[`__view-wrapper-${prev}`];
208208
prevViewWrapper.style.display = 'none';
209+
// Did hide view lifecycle event
210+
const prevView = this.refs['view-0'];
211+
if (prevView && typeof prevView.navigationControllerDidHideView === 'function') {
212+
prevView.navigationControllerDidHideView(this);
213+
}
214+
// Did show view lifecycle event
215+
const nextView = this.refs['view-1'];
216+
if (nextView && typeof nextView.navigationControllerDidShowView === 'function') {
217+
nextView.navigationControllerDidShowView(this);
218+
}
209219
// Unmount the previous view
210220
const mountedViews = [];
211221
mountedViews[prev] = null;
212222
mountedViews[next] = last(this.state.views);
223+
213224
this.setState({
214225
transition: null,
215226
mountedViews: mountedViews
@@ -254,6 +265,16 @@ class NavigationController extends React.Component {
254265
onComplete();
255266
}
256267
};
268+
// Will hide view lifecycle event
269+
const prevView = this.refs['view-0'];
270+
if (prevView && typeof prevView.navigationControllerWillHideView === 'function') {
271+
prevView.navigationControllerWillHideView(this);
272+
}
273+
// Will show view lifecycle event
274+
const nextView = this.refs['view-1'];
275+
if (nextView && typeof nextView.navigationControllerWillShowView === 'function') {
276+
nextView.navigationControllerWillShowView(this);
277+
}
257278
// Built-in transition
258279
if (typeof transition === 'number') {
259280
// Manually transition the views
@@ -281,7 +302,7 @@ class NavigationController extends React.Component {
281302
if (typeof transition === 'function') {
282303
const [prev,next] = this.__viewIndexes;
283304
const prevView = this[`__view-wrapper-${prev}`];
284-
const nextView = this[`__view-wrapper-${next}`];
305+
const nextView = this[`__view-wrapper-${next}`];
285306
transition(prevView, nextView, () => {
286307
this.__animateViewsComplete();
287308
this.__transitionViewsComplete();

0 commit comments

Comments
 (0)