Skip to content

Commit

Permalink
add replace action, improve documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Pavlo Aksonov authored and Pavlo Aksonov committed Oct 5, 2015
1 parent 6275951 commit 72b22ea
Show file tree
Hide file tree
Showing 13 changed files with 453 additions and 350 deletions.
282 changes: 139 additions & 143 deletions .idea/workspace.xml

Large diffs are not rendered by default.

283 changes: 110 additions & 173 deletions Example/.idea/workspace.xml

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions Example/components/Home.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict';

var React = require('react-native');
var {View, Text, StyleSheet} = React;
var Button = require('react-native-button');
var Actions = require('react-native-router-flux').Actions;

class Register extends React.Component {
render(){
return (
<View style={styles.container}>
<Text>Home</Text>
<Button onPress={Actions.pop}>Back</Button>
</View>
);
}
}

var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});

module.exports = Register;
1 change: 1 addition & 0 deletions Example/components/Register.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Register extends React.Component {
return (
<View style={styles.container}>
<Text>Register page</Text>
<Button onPress={Actions.home}>Home</Button>
<Button onPress={Actions.pop}>Back</Button>
</View>
);
Expand Down
2 changes: 2 additions & 0 deletions Example/index.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ var Login = require('./components/Login');
var {Router, Route, Container, Actions, Animations, Schema} = require('react-native-router-flux');
var {NavBar, NavBarModal} = require('./components/NavBar');
var Error = require('./components/Error');
var Home = require('./components/Home');
var TabView = require('./components/TabView');
var TabIcon = require('./components/TabIcon');
var TabBarFlux = require('./components/TabBarFlux');
Expand All @@ -25,6 +26,7 @@ class Example extends React.Component {

<Route name="launch" component={Launch} initial={true} hideNavBar={true} title="Launch"/>
<Route name="register" component={Register} title="Register"/>
<Route name="home" component={Home} title="Home" type="replace"/>
<Route name="login" component={Login} schema="modal"/>
<Route name="register2" component={Register} schema="withoutAnimation"/>
<Route name="error" component={Error} schema="popup"/>
Expand Down
2 changes: 1 addition & 1 deletion Example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"react-native": "^0.11.4",
"react-native-button": "^1.2.1",
"react-native-navbar": "^0.8.0",
"react-native-router-flux": "^0.2.4",
"react-native-router-flux": "^0.3.0",
"react-native-tabs": "^0.0.5"
}
}
42 changes: 40 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
React Native Router using Flux architecture

## Why I need to use it?
- Use Flux actions to push/pop screens with easy syntax like Actions.login for navigation to login screen
- Use Flux actions to replace/push/pop screens with easy syntax like Actions.login for navigation to login screen
- Forget about passing navigator object to all React elements, use actions from anywhere in your UI code.
- 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.
- 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.
- Use popup with Flux actions (see Error popup within Example project)
- Hide nav bar for some screens easily
- Use tab bars for some screens with Flux actions (see demo)
- Simplify processing of data flow in your app (see Getting Started, 4.1)
- 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.

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

## Getting started
1. `npm install react-native-router-flux --save`
2. Define Route for each screen.
2. In top-level index.js:
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.
'component' attribute is React component class which will be created for this route and all route attributes will be passed to it.
'name' is unique name of Route.
2.2 If some your Routes have common attributes, you may define Schema element and just use 'schema' attribute for 'route'
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.
3. In any app screen:
3.1 var {Actions} = require('react-native-router-flux');
3.2 Actions.ACTION_NAME(PARAMS) will call appropriate action and params will be passed to next screen.
4. In your Flux stores (optional):
4.1 You may subscribe to any push/replace/pop 'page' actions in your store.
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:
```
class SearchFilterStore {
constructor(){
this.bindAction(Actions.pop, this.onSet);
}
onSet(data){
this.waitFor(PageStore.dispatchToken);
var route = PageStore.getState().currentRoute;
if (route == 'yourFORM'){
// save data
this.saveData(data);
return true;
}
return false;
}
}
module.exports = alt.createStore(SearchFilterStore, 'SearchFilterStore');
```

Here PageStore.getState().currentRoute is used to check current page, so the store will process only data for needed route.
5 changes: 5 additions & 0 deletions __mocks__/react-native.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ class Navigator extends React.Component {
immediatelyResetRouteStack(routes){
this._currentRoutes = routes;
}
replace(route){
this._currentRoutes.pop();
this._currentRoutes.push(route);
this.setState({route: route});
}
popToRoute(route){
while (this._currentRoutes[this._currentRoutes.length-1] != route){
this._currentRoutes.pop();
Expand Down
122 changes: 96 additions & 26 deletions __tests__/router-tests.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
jest.setMock('../Animations', {FlatFloatFromRight:{}, FlatFloatFromBottom:{}, None:{}});
jest.setMock('alt/components/AltNativeContainer');
var React = require('react/addons');
var alt = require('../alt');
var TestUtils = React.addons.TestUtils;
jest.dontMock('../store');
jest.dontMock('../actions');
Expand All @@ -10,19 +11,40 @@ var Actions = require('../actions');

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

class Store {
constructor(){
this.bindAction(Actions.custom, this.onCustom);
this.data = undefined;
}

onCustom(data){
this.setState({data})
}

}
var TestStore = alt.createStore(Store ,"TestStore");

describe('Router', function() {
it('route', function () {
var router = TestUtils.renderIntoDocument(
<Router>
<Schema name="default" navBar="navBar1" customProp="a"/>
<Schema name="modal" navBar="navBar2" customProp="b" ownProp="c"/>

<Route name="launch" component="launchComponent" hideNavBar={true} customProp="a"/>
<Route name="signin" component="signinComponent" schema="modal"/>
<Route name="signup" component="signupComponent"/>
<Route name="main" component="mainComponent"/>
</Router>
beforeEach(function(){
this.router = TestUtils.renderIntoDocument(
<Router>
<Schema name="default" navBar="navBar1" customProp="a"/>
<Schema name="modal" navBar="navBar2" customProp="b" ownProp="c"/>

<Action name="custom1" navBar="navBar2" customProp="b" ownProp="c" type="custom2"/>
<Action name="custom2" navBar="navBar2" customProp="b" ownProp="c" type="custom3"/>

<Route name="launch" component="launchComponent" hideNavBar={true} customProp="a"/>
<Route name="signin" component="signinComponent" schema="modal"/>
<Route name="signup" component="signupComponent"/>
<Route name="main" component="mainComponent"/>
<Route name="home" component="homeComponent" type="replace" schema="modal"/>
</Router>
);
});

it('route', function () {
var router = this.router;
expect(router.refs.nav.props.initialRoute.name).toEqual('launch');
var len = router.refs.nav.getCurrentRoutes().length;
expect(len).toEqual(1);
Expand Down Expand Up @@ -55,7 +77,7 @@ describe('Router', function() {
var navBar = TestUtils.findRenderedDOMComponentWithTag(
router, 'navBar2');

expect(React.findDOMNode(signinComponent).data).toEqual('Hello world2!');
expect(signinComponent.props.data).toEqual('Hello world2!');
expect(navBar.props.customProp).toEqual("b");
expect(navBar.props.ownProp).toEqual("c");
expect(navBar.props.data).toEqual('Hello world2!');
Expand All @@ -76,23 +98,71 @@ describe('Router', function() {
expect(navBar.props.customProp).toEqual("a");
expect(navBar.props.ownProp).toEqual(undefined);

Actions.pop(2);
Actions.home({data:"Hello world home!", id:111, customProp:'bb'});
len = router.refs.nav.getCurrentRoutes().length;
expect(len).toEqual(2);

expect(router.refs.nav.getCurrentRoutes()[len-1].name).toEqual('signin');
expect(router.refs.nav.getCurrentRoutes()[len-1].passProps.data).toEqual("Hello world!");
expect(router.refs.nav.getCurrentRoutes()[len-1].passProps.id).toEqual(undefined);

Actions.pop();
len = router.refs.nav.getCurrentRoutes().length;
expect(len).toEqual(1);
expect(len).toEqual(4);
var homeComponent = TestUtils.findRenderedDOMComponentWithTag(
router, 'homeComponent');

expect(router.refs.nav.getCurrentRoutes()[len-1].name).toEqual('launch');
launchComponent = TestUtils.findRenderedDOMComponentWithTag(
router, 'launchComponent');
expect(homeComponent.props.data).toEqual('Hello world home!');
navBar = TestUtils.findRenderedDOMComponentWithTag(
router, 'navBar2');
//
//expect(navBar.props.customProp).toEqual("bb");
//expect(navBar.props.ownProp).toEqual("c");
//expect(navBar.props.id).toEqual(111);
//expect(navBar.props.data).toEqual("Hello world home!");
//
//expect(navBar.props.customProp).toEqual("bb");
//expect(navBar.props.ownProp).toEqual("c");
//
//Actions.pop(2);
//len = router.refs.nav.getCurrentRoutes().length;
//expect(len).toEqual(2);
//
//expect(router.refs.nav.getCurrentRoutes()[len-1].name).toEqual('signin');
//expect(router.refs.nav.getCurrentRoutes()[len-1].passProps.data).toEqual("Hello world!");
//expect(router.refs.nav.getCurrentRoutes()[len-1].passProps.id).toEqual(undefined);
//
//Actions.pop();
//len = router.refs.nav.getCurrentRoutes().length;
//expect(len).toEqual(1);
//
//expect(router.refs.nav.getCurrentRoutes()[len-1].name).toEqual('launch');
//launchComponent = TestUtils.findRenderedDOMComponentWithTag(
// router, 'launchComponent');
//
//expect(launchComponent.props.customProp).toEqual("a");
//

expect(launchComponent.props.customProp).toEqual("a");

});

//it('custom actions', function(){
// var router = this.router;
// expect(router.refs.nav.props.initialRoute.name).toEqual('launch');
// var len = router.refs.nav.getCurrentRoutes().length;
// expect(len).toEqual(1);
// var launchComponent = TestUtils.findRenderedDOMComponentWithTag(
// router, 'launchComponent');
//
// expect(launchComponent.props.customProp).toEqual("a");
// var state = TestStore.getState();
// expect(state.data).toEqual(undefined);
//
//
// // no changes within current component should be
// Actions.custom1({url: 'hello world'});
//
// len = router.refs.nav.getCurrentRoutes().length;
// expect(len).toEqual(1);
// var launchComponent = TestUtils.findRenderedDOMComponentWithTag(
// router, 'launchComponent');
//
// expect(launchComponent.props.customProp).toEqual("a");
// state = TestStore.getState();
// expect(state.data.name).toEqual("custom1");
// expect(state.data.data.url).toEqual('hello world');
//
//});
});
3 changes: 3 additions & 0 deletions actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class Actions {
custom(data){
this.dispatch(filterParam(data));
}
replace(data){
this.dispatch(filterParam(data));
}
}

module.exports = alt.createActions(Actions);
12 changes: 8 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ class Router extends React.Component {
data={data:data};
}
var args = {name: name, data:data};
RouterActions.push(args);
var action = child.props.type || 'push';
RouterActions[action](args);
});
}
self.routes[name] = child.props;
Expand All @@ -89,7 +90,7 @@ class Router extends React.Component {
}

onChange(page){
if (page.mode=='push'){
if (page.mode=='push' || page.mode=='replace'){
if (!page.name){
console.error("Page name is not defined for action");
return;
Expand All @@ -109,8 +110,11 @@ class Router extends React.Component {
}
this.setState({modal: element});
} else {
//console.log("PUSH");
this.refs.nav.push(this.getRoute(route, page.data))
if (page.mode == 'replace'){
this.refs.nav.replace(this.getRoute(route, page.data))
} else {
this.refs.nav.push(this.getRoute(route, page.data))
}
}
}
if (page.mode=='pop'){
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-router-flux",
"version": "0.2.5",
"version": "0.3.0",
"description": "React Native Router using Flux architecture",
"main": "index.js",
"scripts": {
Expand Down
9 changes: 9 additions & 0 deletions store.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class RouterStore {
this.bindAction(actions.pop, this.onPop);
this.bindAction(actions.dismiss, this.onDismiss);
this.bindAction(actions.reset, this.onReset);
this.bindAction(actions.replace, this.onReplace);
}

onInit(initial){
Expand All @@ -39,6 +40,14 @@ class RouterStore {
this.setState(data);
}

onReplace(data){
data.mode = 'replace';
this.routes.pop();
this.routes.push(data.name);
this.currentRoute = this.routes[this.routes.length-1];
this.setState(data);
}

onPop(data){
if (!data){
data = {};
Expand Down

0 comments on commit 72b22ea

Please sign in to comment.