Registering React Native Code Push with React Native Navigation by Wix - reactjs

I use react-native-code-push. which is:
This plugin provides client-side integration for the CodePush service,
allowing you to easily add a dynamic update experience to your React
Native app(s).
but In some of native implementations of navigation like react-native-navigation there isn't any root component.
the app will start calling a function like this:
// index.js
import { Navigation } from 'react-native-navigation';
Navigation.startTabBasedApp({
tabs: [
{
label: 'One',
screen: 'example.FirstTabScreen', // this is a registered name for a screen
icon: require('../img/one.png'),
selectedIcon: require('../img/one_selected.png'), // iOS only
title: 'Screen One'
},
{
label: 'Two',
screen: 'example.SecondTabScreen',
icon: require('../img/two.png'),
selectedIcon: require('../img/two_selected.png'), // iOS only
title: 'Screen Two'
}
]
});
// or a single screen app like:
Navigation.registerComponent('example.MainApplication', () => MainComponent);
Navigation.startSingleScreenApp({
screen: {
screen: 'example.MainApplication',
navigatorButtons: {},
navigatorStyle: {
navBarHidden: true
}
},
})
since there is no root component, It's not clear where should I call CodePush, since normally I should wrap my whole root component with CodePush like a higher order component.
what I used to do was:
// index.js
class MyRootComponent extends Component {
render () {
return <MainNavigator/> // a navigator using react-navigation
}
}
let codePushOptions = {
checkFrequency: CodePush.CheckFrequency.ON_APP_RESUME,
installMode: CodePush.InstallMode.ON_NEXT_RESUME
}
export default CodePush(codePushOptions)(MyRootComponent)
Is there a proper way to solve this problem!?
I know I could do this:
Navigation.registerComponent('example.MainApplication', () => CodePush(codePushOptions)(RootComponent));
Navigation.startSingleScreenApp({
screen: {
screen: 'example.MainApplication',
navigatorButtons: {},
navigatorStyle: {
navBarHidden: true
}
},
})
but then I should use a Navigator only for projecting my root component, and It doesn't look like a good idea. I think this problem probably has a best-practice that I'm looking for.
UPDATE
I think there are some complications registering a tab navigator inside a stacknavigator in react-native-navigation at least I couldn't overcome this problem. example tabBasedApp in react-native-navigation with react-native-code-push, will be all that I need.

Thanks for the previous code snippets. I was able to get code push check on app resume and update immediately with react-native-navigation V2 with the below code without requiring wrapper component for codePush. This is the relevant part of the app startup logic.
Navigation.events().registerAppLaunchedListener(() => {
console.log('Navigation: registerAppLaunchedListener ')
start()
})
function checkCodePushUpdate () {
return codePush.sync({
checkFrequency: codePush.CheckFrequency.ON_APP_RESUME,
installMode: codePush.InstallMode.IMMEDIATE,
deploymentKey: CODEPUSH_KEY,
})
}
function start () {
checkCodePushUpdate ()
.then(syncStatus => {
console.log('Start: codePush.sync completed with status: ', syncStatus)
// wait for the initial code sync to complete else we get flicker
// in the app when it updates after it has started up and is
// on the Home screen
startApp()
return null
})
.catch(() => {
// this could happen if the app doesn't have connectivity
// just go ahead and start up as normal
startApp()
})
}
function startApp() {
AppState.addEventListener('change', onAppStateChange)
startNavigation()
}
function onAppStateChange (currentAppState) {
console.log('Start: onAppStateChange: currentAppState: ' + currentAppState)
if (currentAppState === 'active') {
checkCodePushUpdate()
}
}
function startNavigation (registered) {
console.log('Start: startNavigation')
registerScreens()
Navigation.setRoot({
root: {
stack: {
children: [{
component: {
name: 'FirstScreen,
},
}],
},
},
})
}

I got it working this way, although this is for RNN v2
// index.js
import App from './App';
const app = new App();
// App.js
import CodePush from 'react-native-code-push';
import { Component } from 'react';
import { AppState } from 'react-native';
import { Navigation } from 'react-native-navigation';
import configureStore from './app/store/configureStore';
import { registerScreens } from './app/screens';
const appStore = configureStore();
registerScreens(appStore, Provider);
const codePushOptions = {
checkFrequency: CodePush.CheckFrequency.ON_APP_RESUME,
updateDialog: true,
installMode: CodePush.InstallMode.IMMEDIATE
};
export default class App extends Component {
constructor(props) {
super(props);
// Set app state and listen for state changes
this.appState = AppState.currentState;
AppState.addEventListener('change', this.handleAppStateChange);
this.codePushSync();
Navigation.events().registerAppLaunchedListener(() => {
this.startApp();
});
}
handleAppStateChange = nextAppState => {
if (this.appState.match(/inactive|background/) && nextAppState === 'active') {
this.handleOnResume();
}
this.appState = AppState.currentState;
};
codePushSync() {
CodePush.sync(codePushOptions);
}
handleOnResume() {
this.codePushSync();
...
}
startApp() {
Navigation.setRoot({
root: {
stack: {
children: [
{
component: {
name: 'MyApp.Login'
}
}
]
}
}
});
}
}
// app/screens/index.js
import CodePush from 'react-native-code-push';
import { Navigation } from 'react-native-navigation';
import Login from './Login';
function Wrap(WrappedComponent) {
return CodePush(WrappedComponent);
}
export function registerScreens(store, Provider) {
Navigation.registerComponentWithRedux(
'MyApp.Login',
() => Wrap(Login, store),
Provider,
store.store
);
...
}

I found the answer myself.
Look at this example project structure:
.
├── index.js
├── src
| └── app.js
└── screens
├── tab1.html
└── tab2.html
you can register you code-push in index.js.
//index.js
import { AppRegistry } from 'react-native';
import App from './src/app';
import CodePush from 'react-native-code-push'
let codePushOptions = {
checkFrequency: CodePush.CheckFrequency.ON_APP_RESUME,
installMode: CodePush.InstallMode.ON_NEXT_RESUME
}
AppRegistry.registerComponent('YourAppName', () => CodePush(codePushOptions)(App));
now you can start react-native-navigation in app.js like this:
import {Navigation} from 'react-native-navigation';
import {registerScreens, registerScreenVisibilityListener} from './screens';
registerScreens();
registerScreenVisibilityListener();
const tabs = [{
label: 'Navigation',
screen: 'example.Types',
icon: require('../img/list.png'),
title: 'Navigation Types',
}, {
label: 'Actions',
screen: 'example.Actions',
icon: require('../img/swap.png'),
title: 'Navigation Actions',
}];
Navigation.startTabBasedApp({
tabs,
tabsStyle: {
tabBarBackgroundColor: '#003a66',
tabBarButtonColor: '#ffffff',
tabBarSelectedButtonColor: '#ff505c',
tabFontFamily: 'BioRhyme-Bold',
},
appStyle: {
tabBarBackgroundColor: '#003a66',
navBarButtonColor: '#ffffff',
tabBarButtonColor: '#ffffff',
navBarTextColor: '#ffffff',
tabBarSelectedButtonColor: '#ff505c',
navigationBarColor: '#003a66',
navBarBackgroundColor: '#003a66',
statusBarColor: '#002b4c',
tabFontFamily: 'BioRhyme-Bold',
}
});

Related

How to add if condition in "initialRouteName" drawer React Native

i'm new in react native. i want to add if condition in initialRouteName like code below. when "notification" variable is null move to "MemberProfile" page. but if "notification" variable is not null move to "ReviewMember" page. i try it code but still move to "MemberProfile" page. any solution?.
this is my code
var notification = null;
class DrawerMember extends Component {
constructor(props) {
super(props);
this.state = {
notifData: null
};
this.callCheck();
}
async callCheck() {
await AsyncStorage.getItem("#tryCode:notification", (err, result) => {
if (result != null) {
this.setState({
notifData: "testing data"
});
}
});
}
render() {
notification = this.state.notifData;
return <Root />;
}
}
const Root = createDrawerNavigator(
{
MemberProfile: {
screen: MemberProfileScreen
},
ReviewMember: {
screen: ReviewScreen
}
},
{
drawerPosition: "right",
initialRouteName: notification == null ? "MemberProfile" : "ReviewMember",
contentComponent: props => <SideBar {...props} />,
}
);
export default DrawerMember;
I think Root is created before the async function returns so notification is always null.
A possible way to solve this problem would be to use a SwitchNavigator as the first screen in your drawer. This navigator would be responsible for loading the notification and redirecting to the right screen.
Something along the lines of:
import React from 'react';
import { View, AsyncStorage, ActivityIndicator, StatusBar } from 'react-native';
export default class DummySwitch extends React.Component {
async componentDidMount() {
this.listener = this.props.navigation.addListener('willFocus', async () => {
const notification = await AsyncStorage.getItem('#tryCode:notification');
if (notification === null) {
this.props.navigation.navigate('MemberProfile');
}
else {
this.props.navigation.navigate('ReviewMember');
}
});
}
render() {
return (
<View>
<ActivityIndicator />
<StatusBar barStyle='default' />
</View>
);
}
}
As you can see, the switch screen just displays a loading button while accessing the async storage and deciding which route to take.
Then you define the drawer as usual but you add the switch screen as the initial route. You can also hide the label if you want by defining your own drawerLabel:
export default createDrawerNavigator({
Switch: {
screen: Switch,
navigationOptions: () => ({
drawerLabel: () => null,
}),
},
MemberProfile: {
screen: MemberProfileScreen,
},
ReviewMember: {
screen: ReviewScreen,
},
}, { initialRouteName: 'Switch' });
This is it, the drawer now selects the route based on your async storage.

Render base component based on .env config varibles

I want to show the root component of the App based on config variable from react-native-config
I want to achieve something like this. I have a IS_STORYBOOK variable in the .env file, i want to setup my environment so i can just set the value from config and switch to main application and storyboard mode in my react-native application.
By doing this way.. i am getting this error bundling failed: SyntaxError: D:\Projects\React\React-Native Sample app\MobileApp\App.js: 'import' and 'export' may only appear at the top level (62:1)
//App.js
import React from 'react'; // eslint-disable-line
import { Provider } from 'react-redux';
import { pushNotifications } from './src/global/services';
import configureStore from './src/store/configureStore';
import {StackNavigator, createDrawerNavigator } from 'react-navigation'
import { generateStack } from './src/navigation/routesBuilder'
import Drawer from './src/components/drawer/container'
import {items} from './src/components/drawer/draweritems';
import DrawerIcon from './src/components/navIcons/drawerIcon'
import {data} from './src/global/data'
import {scale} from './src/utils/scale'
import StoryBook from './storybook';
import Config from 'react-native-config'
const store = configureStore();
pushNotifications.configure();
data.populateData();
const drawerRoutes = {"app.home":{
screen:generateStack("app.home", "Home", true,true)
}}
for(var i=0; i<items.length; i++){
drawerRoutes[items[i].navigateTo] = {
screen : generateStack(items[i].navigateTo, items[i].title, true, true),
}
}
const RootStack = StackNavigator({
Splash: {
screen: generateStack('app.splash', '', false, false),
navigationOptions:{
header: null
}
},
Auth: {
screen : generateStack('auth.login', '', false, false),
navigationOptions:{
header: null
}
},
Home:{
screen : createDrawerNavigator({
...drawerRoutes
},
{
drawerOpenRoute: 'DrawerOpen',
drawerCloseRoute: 'DrawerClose',
drawerToggleRoute: 'DrawerToggle',
drawerPosition:'left',
drawerWidth:scale(300),
drawerIcon : (<DrawerIcon />),
contentComponent: (props) => <Drawer {...props}/>
}),
}
}, {
headerMode:
'none'
});
export default class App extends React.Component {
render() {
if(Config.IS_STORYBOOK){
return <StoryBoard />
} else {
return(
<Provider store={store}>
<RootStack />
</Provider>
)
}
}
}
//storybook.js
import { AppRegistry } from "react-native";
import { getStorybookUI, configure } from "#storybook/react-native";
import { loadStories } from "./storyLoader";
configure(() => {
loadStories();
}, module);
const StorybookUI = getStorybookUI({
port: 7007,
host: "localhost",
onDeviceUI: true,
resetStorybook: true
});
AppRegistry.registerComponent("sampleproject", () => StorybookUI);
export { StorybookUI as default };
// .env
IS_STORYBOOK=false
React components are bundled during build-time and not during run-time.
You cannot conditionally export or import your components. Instead you should conditionally render stuff in your components.
Something like this might work for you
import StoryBook from './storybook';
export default class App extends React.Component {
render() {
if(!Config.IS_STORYBOOK){
return(
<Provider store={store}>
<RootStack />
</Provider>
)
} else {
return <StoryBook />
}
}
}

How do you navigate to another component that does not receive the props of react navigation?

I'm working with React Native and React Navigation.
I have a component called App.js in which I declare the Drawer Navigation of React-Navigation.
In this I have an option to log out but I can not navigate to another component after removing the AsyncStorage
Does anyone know how to achieve it?
Thank you.
This is my code:
App.js
import { createDrawerNavigator, DrawerItems, NavigationActions } from 'react-navigation';
const customDrawerComponent = (props) => (
<SafeAreaView style={{ flex: 1 }}>
<ScrollView>
<DrawerItems
{...props}
/>
<TouchableOpacity style={styles.button} onPress={this.logOut} >
<Text> Logout </Text>
</TouchableOpacity>
</ScrollView>
</SafeAreaView>
);
logOut = () => {
// NOT WORKS
// this.props.navigation.navigate('Login')
//NOT WORKS:
this.myAction();
}
myAction = () => {
const nav = NavigationActions.navigate({
routeName: 'App',
});
return nav;
};
const AppDrawNavigator = createDrawerNavigator(
{
MainComponent: { screen: MainComponent,
navigationOptions: ({navigation}) => ({
drawerLockMode: 'locked-closed'
}) },
Login: { screen: LoginComponent,
navigationOptions: ({navigation}) => ({
drawerLockMode: 'locked-closed'
}) },
User: { screen: UsersComponent }
},
{
contentComponent: customDrawerComponent,
}
);
make this as a class like
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
}
}
From your question I understand that either you want to :-
navigate from outside the components
navigate from components which do not have navigation prop.
For this I have tried 2 solutions and both work extremely fine though I based towards the second one.
First Solution
Use withNavigation from react-navigation package. If your components are deeply nested they wont have navigation prop unless u specify them manually or put them in context ;passing navigation prop becomes a real pain. So instead use withNavigation and your component would have navigation prop.
import {withNavigation} from "react-navigation";
const Component = ({navigation}) => {
const onPress = () => {
navigation.navigate(//ROUTE_NAME//)
}
return (
<TouchableOpacity onPress={onPress}>
<Text>Navigate</Text>
</TouchableOpacity>
)
}
export default withNavigation(Component);
Second Solution
Create a helper script and use that.
"use strict";
import React from "react";
import {NavigationActions} from "react-navigation";
let _container; // eslint-disable-line
export const navigation = {
mapProps: (SomeComponent) => {
return class extends React.Component {
static navigationOptions = SomeComponent.navigationOptions; // better use hoist-non-react-statics
render () {
const {navigation: {state: {params}}} = this.props;
return <SomeComponent {...params} {...this.props} />;
}
}
},
setContainer: (container) => {
_container = container;
},
reset: (routeName, params) => {
_container.dispatch(
NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({
type: "Navigation/NAVIGATE",
routeName,
params
})
]
})
);
},
goBack: () => {
_container.dispatch(NavigationActions.back());
},
navigate: (routeName, params) => {
_container.dispatch(
NavigationActions.navigate({
type: "Navigation/NAVIGATE",
routeName,
params
})
);
},
navigateDeep: (actions) => {
_container.dispatch(
actions.reduceRight(
(prevAction, action) =>
NavigationActions.navigate({
type: "Navigation/NAVIGATE",
routeName: action.routeName,
params: action.params,
action: prevAction
}),
undefined
)
);
},
getCurrentRoute: () => {
if (!_container || !_container.state.nav) {
return null;
}
return _container.state.nav.routes[_container.state.nav.index] || null;
}
};
In your parent component when you mount the navigation call following:-
"use strict";
import React from "react";
import App from "./routes";
import {navigation} from "utils";
class Setup extends React.Component {
render () {
return (
<App
ref={navigatorRef => {
navigation.setContainer(navigatorRef);
}}
/>
);
}
}
export default App;
Now, in your components you can directly use helpers from this script itself and navigation would be accessibly globally now.
import {navigate} from "utils/navigation";
For more details you can this thread
Your logout function is declared outside of the Navigator. This means you don't have access to the navigation prop there. However, your customDrawerComponent is a screen of your Navigator and it should have access to it.
So you can try something like this (props here are the props passed to the customDrawerComponent):
onPress={()=> {props.navigation.navigate("Login")}}
Plus your App.js seems kind of strange since you're not exporting any component. Have you pasted the whole code of App.js or just parts of it?

Tracker.autorun not working inside componentDidMount of react

Tracker.autorun not working inside componentDidMount of react when I specify the projection (fields) for output. But the same works when I dont have any projection on mongo query.
This works:
Meteor.subscribe('quotes');
this.quotesTracker = Tracker.autorun(() => {
const quotes = Quotes.find(
{instrument_token: 12374274},
{
sort: {timestamp: 1},
limit: 5000
}
).fetch();
This doesnt work
Meteor.subscribe('quotes');
this.quotesTracker =Tracker.autorun(() => {
const quotes = Quotes.find(
{instrument_token: 12374274},
{
fields: {
last_price: 1,
timestamp: 1,
},
sort: {timestamp: 1},
limit: 5000
}
).fetch();
What am I missing here?
I don't think Meteor's tracker works well with ReactJS, as their mechanism of re-render is different.
You might want to use this package.
https://github.com/meteor/react-packages/tree/devel/packages/react-meteor-data
You can use it like so.
import { Meteor } from 'meteor/meteor';
import { mount } from 'react-mounter';
import { withTracker } from 'meteor/react-meteor-data';
import { IndexPage } from "./index-page";
FlowRouter.route('/', {
action: () => {
mount(IndexPageContainer, {});
}
});
export const IndexPageContainer = withTracker(() => {
Meteor.subscribe('whatever');
return {
Meteor: {
collection: {
whatever: Whatever.find().fetch()
},
user: Meteor.user(),
userId: Meteor.userId(),
status: Meteor.status(),
loggingIn: Meteor.loggingIn()
}
};
})(IndexPage);
Where IndexPage is your actual component.
You can then access the db by this.props.Meteor.collection.whatever.find()

How to use Drawer customComponent. I think Im doing what docs suggest but doesn't work

Im trying to customize the Drawer for my Expo RN App but after hours and hours of endless trying I decided to just ask for help.
I see im doing just whats docs and other people around the internet are doing:
DrawerNavigator(RouteConfigs, DrawerNavigatorConfig)
but this in my case, it just does not work. All I see is this big red error: Error Screen
The problem is located in my main navigator:
const MainNavigator = DrawerNavigator(
{
pedidosNavigator: { screen: PedidosNavigator },
menuNavigator: { screen: MenuNavigator },
},
{
contentComponent: props => <PerfilScreen {...props} />,
}
);
If I set it up this way, it works fine but without customization:
const MainNavigator = DrawerNavigator(
{
pedidosNavigator: { screen: PedidosNavigator },
menuNavigator: { screen: MenuNavigator },
})
The problem is when I add this object as the docs suggest:
{
contentComponent: props => <PerfilScreen {...props} />,
}
Or even when I try to add a width property:
{
drawerWidth: 300
}
What the hell Im doing wrong? Here is my full code
import React from 'react';
import { TabNavigator, StackNavigator, DrawerNavigator } from 'react-navigation';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import ReduxThunk from 'redux-thunk';
import reducers from './src/components/reducers';
import MenuScreen from './screens/MenuScreen';
import PerfilScreen from './screens/PerfilScreen';
import PedidosNuevosScreen from './screens/PedidosNuevosScreen';
import CreateEditMenuScreen from './screens/CreateEditMenuScreen';
import AcceptDeclineScreen from './screens/AcceptDeclineScreen';
import PedidosEnCursoScreen from './screens/PedidosEnCursoScreen';
export default class App extends React.Component {
render() {
const MenuNavigator = TabNavigator({
menu: { screen: MenuScreen },
create: { screen: CreateEditMenuScreen }
}
);
const PedidosNavigator = TabNavigator(
{
entrantes: { screen: StackNavigator(
{
entrantes: { screen: PedidosNuevosScreen },
nuevoPedido: { screen: AcceptDeclineScreen }
}
) },
enCurso: { screen: PedidosEnCursoScreen }
}
);
const MainNavigator = DrawerNavigator(
{
pedidosNavigator: { screen: PedidosNavigator },
menuNavigator: { screen: MenuNavigator },
},
{
contentComponent: props => <PerfilScreen {...props} />,
}
);
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
return (
<Provider store={store}>
<MainNavigator />
</Provider>
);
}
}
Thanks for your help!
After even more hours of looking for an answer, I did find the answer. Drawer Navigator must contain aditional properties to work.
If you are like me, and want to customize the drawer you will also have to include this props to your Drawer Navigator:
{
initialRouteName: 'YourMainRouteAsString',
contentComponent: YourCustomizedComponent,
drawerOpenRoute: 'DrawerOpen',
drawerCloseRoute: 'DrawerClose',
drawerToggleRoute: 'DrawerToggle'
}

Resources