Skip to content

Commit 72b22ea

Browse files
Pavlo AksonovPavlo Aksonov
authored andcommitted
add replace action, improve documentation
1 parent 6275951 commit 72b22ea

File tree

13 files changed

+453
-350
lines changed

13 files changed

+453
-350
lines changed

.idea/workspace.xml

Lines changed: 139 additions & 143 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Example/.idea/workspace.xml

Lines changed: 110 additions & 173 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Example/components/Home.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use strict';
2+
3+
var React = require('react-native');
4+
var {View, Text, StyleSheet} = React;
5+
var Button = require('react-native-button');
6+
var Actions = require('react-native-router-flux').Actions;
7+
8+
class Register extends React.Component {
9+
render(){
10+
return (
11+
<View style={styles.container}>
12+
<Text>Home</Text>
13+
<Button onPress={Actions.pop}>Back</Button>
14+
</View>
15+
);
16+
}
17+
}
18+
19+
var styles = StyleSheet.create({
20+
container: {
21+
flex: 1,
22+
justifyContent: 'center',
23+
alignItems: 'center',
24+
backgroundColor: '#F5FCFF',
25+
},
26+
welcome: {
27+
fontSize: 20,
28+
textAlign: 'center',
29+
margin: 10,
30+
},
31+
instructions: {
32+
textAlign: 'center',
33+
color: '#333333',
34+
marginBottom: 5,
35+
},
36+
});
37+
38+
module.exports = Register;

Example/components/Register.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class Register extends React.Component {
1010
return (
1111
<View style={styles.container}>
1212
<Text>Register page</Text>
13+
<Button onPress={Actions.home}>Home</Button>
1314
<Button onPress={Actions.pop}>Back</Button>
1415
</View>
1516
);

Example/index.ios.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ var Login = require('./components/Login');
88
var {Router, Route, Container, Actions, Animations, Schema} = require('react-native-router-flux');
99
var {NavBar, NavBarModal} = require('./components/NavBar');
1010
var Error = require('./components/Error');
11+
var Home = require('./components/Home');
1112
var TabView = require('./components/TabView');
1213
var TabIcon = require('./components/TabIcon');
1314
var TabBarFlux = require('./components/TabBarFlux');
@@ -25,6 +26,7 @@ class Example extends React.Component {
2526

2627
<Route name="launch" component={Launch} initial={true} hideNavBar={true} title="Launch"/>
2728
<Route name="register" component={Register} title="Register"/>
29+
<Route name="home" component={Home} title="Home" type="replace"/>
2830
<Route name="login" component={Login} schema="modal"/>
2931
<Route name="register2" component={Register} schema="withoutAnimation"/>
3032
<Route name="error" component={Error} schema="popup"/>

Example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"react-native": "^0.11.4",
1010
"react-native-button": "^1.2.1",
1111
"react-native-navbar": "^0.8.0",
12-
"react-native-router-flux": "^0.2.4",
12+
"react-native-router-flux": "^0.3.0",
1313
"react-native-tabs": "^0.0.5"
1414
}
1515
}

README.md

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
React Native Router using Flux architecture
33

44
## Why I need to use it?
5-
- Use Flux actions to push/pop screens with easy syntax like Actions.login for navigation to login screen
5+
- Use Flux actions to replace/push/pop screens with easy syntax like Actions.login for navigation to login screen
66
- Forget about passing navigator object to all React elements, use actions from anywhere in your UI code.
77
- Configure all of your screens ("routes") once (define animations, nav bars, etc.), at one place and then just use short actions commands. For example if you use some special animation for Login screen, you don't need to code it anywhere where an user should be redirected to login screen.
88
- Use route "schemas" to define common property for some screens. For example some screens are "modal" (i.e. have animation from bottom and have Cancel/Close nav button), so you could define group for them to avoid any code repeatition.
99
- Use popup with Flux actions (see Error popup within Example project)
1010
- Hide nav bar for some screens easily
1111
- Use tab bars for some screens with Flux actions (see demo)
12+
- Simplify processing of data flow in your app (see Getting Started, 4.1)
13+
- Define your custom Flux actions (like fetch or validation actions) with the component too, so you will have all app actions in the one place.
1214

1315
## Example
1416
![demo-2](https://cloud.githubusercontent.com/assets/1321329/9466261/de64558e-4b33-11e5-8ada-0fcd49442769.gif)
@@ -25,6 +27,7 @@ var Login = require('./components/Login');
2527
var {Router, Route, Container, Actions, Animations, Schema} = require('react-native-router-flux');
2628
var {NavBar, NavBarModal} = require('./components/NavBar');
2729
var Error = require('./components/Error');
30+
var Home = require('./components/Home');
2831
var TabView = require('./components/TabView');
2932
var TabIcon = require('./components/TabIcon');
3033
var TabBarFlux = require('./components/TabBarFlux');
@@ -42,6 +45,7 @@ class Example extends React.Component {
4245
4346
<Route name="launch" component={Launch} initial={true} hideNavBar={true} title="Launch"/>
4447
<Route name="register" component={Register} title="Register"/>
48+
<Route name="home" component={Home} title="Home" type="replace"/>
4549
<Route name="login" component={Login} schema="modal"/>
4650
<Route name="register2" component={Register} schema="withoutAnimation"/>
4751
<Route name="error" component={Error} schema="popup"/>
@@ -99,4 +103,38 @@ module.exports = Launch;
99103

100104
## Getting started
101105
1. `npm install react-native-router-flux --save`
102-
2. Define Route for each screen.
106+
2. In top-level index.js:
107+
2.1 Define Route for each app screen. Its 'type' attribute is 'push' by default, but you also could define 'replace', so navigator will replace current route with new route.
108+
'component' attribute is React component class which will be created for this route and all route attributes will be passed to it.
109+
'name' is unique name of Route.
110+
2.2 If some your Routes have common attributes, you may define Schema element and just use 'schema' attribute for 'route'
111+
2.3 If you want to define some your custom actions, just add 'Action' element inside Router. That action will not be processed by the component, it will call Actions.custom({name:ACTION_NAME, ...params}) so you could handle it in your stores. It allows to add Fetch actions (which downloads web content), etc.
112+
3. In any app screen:
113+
3.1 var {Actions} = require('react-native-router-flux');
114+
3.2 Actions.ACTION_NAME(PARAMS) will call appropriate action and params will be passed to next screen.
115+
4. In your Flux stores (optional):
116+
4.1 You may subscribe to any push/replace/pop 'page' actions in your store.
117+
It could be necessary if you want to process user data somehow. For example, if some component manages user form and have "Save" button which should store that data and pop the screen, you may use Actions.pop(this.state) in that component and then subscribe to Actions.pop actions within store:
118+
```
119+
class SearchFilterStore {
120+
constructor(){
121+
this.bindAction(Actions.pop, this.onSet);
122+
}
123+
124+
onSet(data){
125+
this.waitFor(PageStore.dispatchToken);
126+
var route = PageStore.getState().currentRoute;
127+
128+
if (route == 'yourFORM'){
129+
// save data
130+
131+
this.saveData(data);
132+
return true;
133+
}
134+
return false;
135+
}
136+
}
137+
module.exports = alt.createStore(SearchFilterStore, 'SearchFilterStore');
138+
```
139+
140+
Here PageStore.getState().currentRoute is used to check current page, so the store will process only data for needed route.

__mocks__/react-native.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ class Navigator extends React.Component {
2929
immediatelyResetRouteStack(routes){
3030
this._currentRoutes = routes;
3131
}
32+
replace(route){
33+
this._currentRoutes.pop();
34+
this._currentRoutes.push(route);
35+
this.setState({route: route});
36+
}
3237
popToRoute(route){
3338
while (this._currentRoutes[this._currentRoutes.length-1] != route){
3439
this._currentRoutes.pop();

__tests__/router-tests.js

Lines changed: 96 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
jest.setMock('../Animations', {FlatFloatFromRight:{}, FlatFloatFromBottom:{}, None:{}});
22
jest.setMock('alt/components/AltNativeContainer');
33
var React = require('react/addons');
4+
var alt = require('../alt');
45
var TestUtils = React.addons.TestUtils;
56
jest.dontMock('../store');
67
jest.dontMock('../actions');
@@ -10,19 +11,40 @@ var Actions = require('../actions');
1011

1112
var {Router, Route, Schema, Action} = require('../index');
1213

14+
class Store {
15+
constructor(){
16+
this.bindAction(Actions.custom, this.onCustom);
17+
this.data = undefined;
18+
}
19+
20+
onCustom(data){
21+
this.setState({data})
22+
}
23+
24+
}
25+
var TestStore = alt.createStore(Store ,"TestStore");
26+
1327
describe('Router', function() {
14-
it('route', function () {
15-
var router = TestUtils.renderIntoDocument(
16-
<Router>
17-
<Schema name="default" navBar="navBar1" customProp="a"/>
18-
<Schema name="modal" navBar="navBar2" customProp="b" ownProp="c"/>
19-
20-
<Route name="launch" component="launchComponent" hideNavBar={true} customProp="a"/>
21-
<Route name="signin" component="signinComponent" schema="modal"/>
22-
<Route name="signup" component="signupComponent"/>
23-
<Route name="main" component="mainComponent"/>
24-
</Router>
28+
beforeEach(function(){
29+
this.router = TestUtils.renderIntoDocument(
30+
<Router>
31+
<Schema name="default" navBar="navBar1" customProp="a"/>
32+
<Schema name="modal" navBar="navBar2" customProp="b" ownProp="c"/>
33+
34+
<Action name="custom1" navBar="navBar2" customProp="b" ownProp="c" type="custom2"/>
35+
<Action name="custom2" navBar="navBar2" customProp="b" ownProp="c" type="custom3"/>
36+
37+
<Route name="launch" component="launchComponent" hideNavBar={true} customProp="a"/>
38+
<Route name="signin" component="signinComponent" schema="modal"/>
39+
<Route name="signup" component="signupComponent"/>
40+
<Route name="main" component="mainComponent"/>
41+
<Route name="home" component="homeComponent" type="replace" schema="modal"/>
42+
</Router>
2543
);
44+
});
45+
46+
it('route', function () {
47+
var router = this.router;
2648
expect(router.refs.nav.props.initialRoute.name).toEqual('launch');
2749
var len = router.refs.nav.getCurrentRoutes().length;
2850
expect(len).toEqual(1);
@@ -55,7 +77,7 @@ describe('Router', function() {
5577
var navBar = TestUtils.findRenderedDOMComponentWithTag(
5678
router, 'navBar2');
5779

58-
expect(React.findDOMNode(signinComponent).data).toEqual('Hello world2!');
80+
expect(signinComponent.props.data).toEqual('Hello world2!');
5981
expect(navBar.props.customProp).toEqual("b");
6082
expect(navBar.props.ownProp).toEqual("c");
6183
expect(navBar.props.data).toEqual('Hello world2!');
@@ -76,23 +98,71 @@ describe('Router', function() {
7698
expect(navBar.props.customProp).toEqual("a");
7799
expect(navBar.props.ownProp).toEqual(undefined);
78100

79-
Actions.pop(2);
101+
Actions.home({data:"Hello world home!", id:111, customProp:'bb'});
80102
len = router.refs.nav.getCurrentRoutes().length;
81-
expect(len).toEqual(2);
82-
83-
expect(router.refs.nav.getCurrentRoutes()[len-1].name).toEqual('signin');
84-
expect(router.refs.nav.getCurrentRoutes()[len-1].passProps.data).toEqual("Hello world!");
85-
expect(router.refs.nav.getCurrentRoutes()[len-1].passProps.id).toEqual(undefined);
86-
87-
Actions.pop();
88-
len = router.refs.nav.getCurrentRoutes().length;
89-
expect(len).toEqual(1);
103+
expect(len).toEqual(4);
104+
var homeComponent = TestUtils.findRenderedDOMComponentWithTag(
105+
router, 'homeComponent');
90106

91-
expect(router.refs.nav.getCurrentRoutes()[len-1].name).toEqual('launch');
92-
launchComponent = TestUtils.findRenderedDOMComponentWithTag(
93-
router, 'launchComponent');
107+
expect(homeComponent.props.data).toEqual('Hello world home!');
108+
navBar = TestUtils.findRenderedDOMComponentWithTag(
109+
router, 'navBar2');
110+
//
111+
//expect(navBar.props.customProp).toEqual("bb");
112+
//expect(navBar.props.ownProp).toEqual("c");
113+
//expect(navBar.props.id).toEqual(111);
114+
//expect(navBar.props.data).toEqual("Hello world home!");
115+
//
116+
//expect(navBar.props.customProp).toEqual("bb");
117+
//expect(navBar.props.ownProp).toEqual("c");
118+
//
119+
//Actions.pop(2);
120+
//len = router.refs.nav.getCurrentRoutes().length;
121+
//expect(len).toEqual(2);
122+
//
123+
//expect(router.refs.nav.getCurrentRoutes()[len-1].name).toEqual('signin');
124+
//expect(router.refs.nav.getCurrentRoutes()[len-1].passProps.data).toEqual("Hello world!");
125+
//expect(router.refs.nav.getCurrentRoutes()[len-1].passProps.id).toEqual(undefined);
126+
//
127+
//Actions.pop();
128+
//len = router.refs.nav.getCurrentRoutes().length;
129+
//expect(len).toEqual(1);
130+
//
131+
//expect(router.refs.nav.getCurrentRoutes()[len-1].name).toEqual('launch');
132+
//launchComponent = TestUtils.findRenderedDOMComponentWithTag(
133+
// router, 'launchComponent');
134+
//
135+
//expect(launchComponent.props.customProp).toEqual("a");
136+
//
94137

95-
expect(launchComponent.props.customProp).toEqual("a");
96138

97139
});
140+
141+
//it('custom actions', function(){
142+
// var router = this.router;
143+
// expect(router.refs.nav.props.initialRoute.name).toEqual('launch');
144+
// var len = router.refs.nav.getCurrentRoutes().length;
145+
// expect(len).toEqual(1);
146+
// var launchComponent = TestUtils.findRenderedDOMComponentWithTag(
147+
// router, 'launchComponent');
148+
//
149+
// expect(launchComponent.props.customProp).toEqual("a");
150+
// var state = TestStore.getState();
151+
// expect(state.data).toEqual(undefined);
152+
//
153+
//
154+
// // no changes within current component should be
155+
// Actions.custom1({url: 'hello world'});
156+
//
157+
// len = router.refs.nav.getCurrentRoutes().length;
158+
// expect(len).toEqual(1);
159+
// var launchComponent = TestUtils.findRenderedDOMComponentWithTag(
160+
// router, 'launchComponent');
161+
//
162+
// expect(launchComponent.props.customProp).toEqual("a");
163+
// state = TestStore.getState();
164+
// expect(state.data.name).toEqual("custom1");
165+
// expect(state.data.data.url).toEqual('hello world');
166+
//
167+
//});
98168
});

actions.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ class Actions {
4040
custom(data){
4141
this.dispatch(filterParam(data));
4242
}
43+
replace(data){
44+
this.dispatch(filterParam(data));
45+
}
4346
}
4447

4548
module.exports = alt.createActions(Actions);

0 commit comments

Comments
 (0)