I have a React Native application which I have implemented. Currently the app opens up on a loading screen which after mounting checks the firebase.auth().onAuthStateChanged(...) feature.
The app basically decides whether or not to got to the login screen or to main screen depending on whether or not the user is already authenticated.
It is implemented like this:
Main Navigator:
const MainNavigator = TabNavigator({
auth: {
screen: TabNavigator({
login: { screen: LoginScreen },
signup: { screen: SignupScreen }
}, {
initialRouteName: 'login',
tabBarPosition: 'top',
lazy: true,
animationEnabled: true,
swipeEnabled: true,
tabBarOptions: {
labelStyle: { fontSize: 12 },
showIcon: true,
iconStyle: { width: 30, height: 30 }
}
})
},
main: {
screen: StackNavigator({
notes: { screen: NotesScreen }
}, {
initialRouteName: 'notes'
})
},
loading: { screen: LoadingScreen }
}, {
initialRouteName: 'loading',
lazy: true,
swipeEnabled: false,
animationEnabled: false,
navigationOptions: {
tabBarVisible: false
}
});
Loading Screen:
class LoadingScreen extends Component {
componentDidMount() {
const { navigate } = this.props.navigation;
firebase.auth().onAuthStateChanged(user => {
if (user) {
navigate('main');
} else {
navigate('auth');
}
});
}
render() {
return (
<View style={styles.spinnerStyle}>
<Spinner size="large" />
</View>
);
}
}
const styles = {
spinnerStyle: {
flexDirection: 'row',
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
};
This works well except for one issue.
When I press the hardware back button for Android, it goes to the application loading screen which obvious is undesired. How do I prevent that?
EDIT:
I've tried the following and it didn't work either:
const resetAction = (routeName) => NavigationActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName })],
key: null
});
class LoadingScreen extends Component {
componentDidMount() {
const { dispatch } = this.props.navigation;
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.props.setUser(user);
dispatch(resetAction('main'));
} else {
dispatch(resetAction('auth'));
}
});
}
render() {
return (
<View style={styles.spinnerStyle}>
<Spinner size="large" />
</View>
);
}
}
use a switch navigator until the user logs in(loading and login page ) successsfully after that use a stack navigator(user homepage and otherpages which follow).
switchNavigator(loading, login, stackNavigator)
stackNavigator(user homepage,....)
Try a custom navigation component with custom back button support. Dont forget to add the reducer to yoru combine reducers function.
Create a navigation component:
import React, { Component } from 'react';
import { BackHandler } from 'react-native';
import { connect } from 'react-redux';
import { addNavigationHelpers } from 'react-navigation';
import MainNavigator from './MainNavigator';
class AppWithNavigationState extends Component {
componentDidMount () {
BackHandler.addEventListener('hardwareBackPress', () => {
this.props.dispatch({
type: 'Navigation/BACK'
});
return true;
});
}
componentWillUnmount () {
BackHandler.removeEventListener('hardwareBackPress');
}
render () {
return (
<MainNavigator navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav
})}/>
);
}
}
export default connect((state) => ({ nav: state.nav }))(AppWithNavigationState);
Create a navigation reducer:
import { NavigationActions } from 'react-navigation';
import MainNavigator from './MainNavigator';
import { NAVIGATION_ON_SIGN_IN } from '../redux/actions/ActionTypes';
import { BackHandler } from 'react-native';
const initialState = MainNavigator.router.getStateForAction(MainNavigator.router.getActionForPathAndParams('loading'));
function appShouldClose (nextState) {
const { index, routes } = nextState;
return index === 0 || routes[1].routeName === 'auth';
}
export default (state = initialState, action) => {
const { router } = MainNavigator;
let nextState;
switch (action.type) {
case NavigationActions.BACK:
nextState = router.getStateForAction(action, state);
appShouldClose(nextState) && BackHandler.exitApp();
break;
default:
nextState = router.getStateForAction(action, state);
}
return nextState || state;
};
it is my solution :)
I have StageArea page. it is bridge between from login to timeline . User is not login then go to LoginPage. User is login then go to Timeline. User press back button then again go to TimeLine page not go to login page .( Sory for my english)
import React, { Component } from 'react';
import { View } from 'react-native';
import LoginForm from './LoginForm';
import Timeline from './Timeline';
import firebase from 'firebase';
import InitialPage from './InitialPage'
class StageArea extends Component {
state = {isLoggin:''};
componentWillMount(){
firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.setState({ isLoggin:true})
}else {
this.setState({ isLoggin:false})
}
})
}
render() {
if(this.state.isLoggin)
{
return(<Timeline/>);
}
else if (this.state.isLoggin===false) {
return(<LoginForm/>);
}
}
}
export default StageArea;
Write the code below ,
static navigationOptions = {
header:null
};
Just before
render() {
return (
on the NotesScreen,There will not be any back button.
Related
Hi I'm currently trying to add users to a page using react native, redux, and firebase. When User 1 clicks join, they get added to the feed and likewise for other users. However, a problem I'm facing is when user 2 clicks join, they get added to the feed but don't get displayed on user 1's page unless the user 1 refocuses on the page after going away.
Here is my code for the page itself in react native
import React, { Component } from 'react';
import { Text, View, Button, TouchableOpacity, SafeAreaView, ScrollView, Image } from 'react-native';
import styles from '../styles.js'
import { connect } from 'react-redux'
import { FlatList } from 'react-native-gesture-handler';
import { FontAwesome5 } from '#expo/vector-icons';
import { Octicons } from '#expo/vector-icons';
import { FontAwesome } from '#expo/vector-icons';
import { addUser, removeUser, getLivingRoomUsers } from '../actions/livingRoomUser.js'
import { bindActionCreators } from 'redux'
class LivingRoom extends React.Component {
constructor(props) {
super(props);
this.state = {
inRoom: false,
isMuted: false
};
}
componentDidMount(){
this._unsubscribe = this.props.navigation.addListener('focus', () => {
this.props.getLivingRoomUsers()
});
}
joinRoom = () => {
this.props.addUser()
this.setState({ inRoom: true });
}
leaveRoom = () => {
this.props.removeUser(this.props.livingRoomUser)
this.setState({ inRoom: false });
}
...
render(){
return (
<View>
<SafeAreaView style={styles.livingRoomUserContainer}>
<FlatList
data={this.props.livingRoomUser.feed}
Here is my actions code for the redux portion:
export const addUser = () => {
return async (dispatch, getState) => {
try {
const { user } = getState()
const id = uuid.v4()
const livingRoomUser = {
id: id,
avatar: user.avatar,
username: user.username,
isMuted: false,
date: new Date().getTime(),
}
db.collection('livingroom').doc(id).set(livingRoomUser)
dispatch({
type: 'ADD_USER', payload: livingRoomUser
})
dispatch(getLivingRoomUsers())
} catch (e) {
alert(e)
}
}
}
export const removeUser = (livingRoomUser) => {
return async (dispatch, getState) => {
try {
db.collection('livingroom').doc(livingRoomUser.id).delete();
dispatch(getLivingRoomUsers())
//get living room users
} catch (e) {
alert(e)
}
}
}
export const getLivingRoomUsers = () => {
return async (dispatch, getState) => {
try {
const livingRoomUsers = await db.collection('livingroom').get()
let array = []
livingRoomUsers.forEach((livingRoomUser) => {
array.push(livingRoomUser.data())
})
dispatch({
type: 'GET_LIVING_ROOM_USERS', payload: orderBy(array, 'date', 'asc')
})
} catch (e) {
alert(e)
}
}
}
To summarize. I want the getUsers to be updated anytime someone adds/removes themself from the page. However, from my implementation currently actions only get updated for the current user and the feed only gets updated when the page is focused. How do I go about this?
use onSnapshot listener on the firestore then you can get the latest updates as the store change
export const getLivingRoomUsers = () => {
return async (dispatch, getState) => {
try {
db.collection('livingroom').onSnapshot(snapshot => {
let array = snapshot.docs.map(d => d.data());
dispatch({
type: 'GET_LIVING_ROOM_USERS',
payload: orderBy(array, 'date', 'asc'),
});
});
} catch (e) {
alert(e);
}
};
};
Trying to toggle open a modal from another component with redux. Almost there but not really sure how to finish it up - been looking around for a clear answer!
On the HomeScreen component (the main component), to activate the openModal method on the AddCircleModal component, causing the Modal to open.
The Modal - AddCircleModal: Using redux, I can successfully close the modal if I open it manually in the code
class AddCircleModal extends React.Component {
state = {
top: new Animated.Value(screenHeight),
modalVisible: false
}
// componentDidMount() {
// this.openModal()
// }
openModal = () => {
Animated.spring(this.state.top, {
toValue: 174
}).start()
this.setState({modalVisible: true})
}
closeModal = () => {
Animated.spring(this.state.top, {
toValue: screenHeight
}).start()
this.setState({modalVisible: false})
}
render() {
return (
<Modal
transparent={true}
visible={this.state.modalVisible}
>
<AnimatedContainer style={{ top: this.state.top, }}>
<Header />
<TouchableOpacity
onPress={this.closeModal}
style={{ position: "absolute", top: 120, left: "50%", marginLeft: -22, zIndex: 1 }}
>
<CloseView style={{ elevation: 10 }}>
<FeatherIcon name="plus" size={24} />
</CloseView>
</TouchableOpacity>
<Body />
</AnimatedContainer>
</Modal>
)
}
}
function mapStateToProps(state) {
return { action: state.action }
}
function mapDispatchToProps(dispatch) {
return {
closeModal: () =>
dispatch({
type: "CLOSE_MODAL"
})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AddCircleModal)
HomeScreen: The other component that I want to toggle from
//redux
import { connect } from 'react-redux'
import styles from './Styles'
class HomeScreen extends React.Component {
constructor() {
super();
this.state = {
};
}
toggleOpenCircleModal = () => {
// this.openModal() - what do I do with this to call the openModal function in the modal component?
console.log('owwwww weeeee')
}
render() {
return (
<SafeAreaView>
<HomeHeader openModal={this.toggleOpenCircleModal}/> - this method is because I'm calling toggleOpenCircleModal from a button in the header of the home screen. It works as it outputs the 'owwwww weeeee' string to the console.
<SafeAreaView style={{ width: '100%', flex: 1}} />
<AddCircleModal />
</SafeAreaView>
);
}
}
function mapStateToProps(state) {
return { action: state.action }
}
function mapDispatchToProps(dispatch) {
return {
openModal: () =>
dispatch({
type: "OPEN_MODAL"
})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(HomeScreen)
modalToggle: The reducer
const initialState = {
action: ""
}
const modalToggle = (state = initialState, action) => {
switch (action.type) {
case "OPEN_MODAL":
return { ...state, action: "openModal" }
case "CLOSE_MODAL":
return { ...state, action: "closeModal" }
default:
return state
}
}
export default modalToggle
Right now, your components are not using redux store properly.
When you use mapStateToProps, you can access every redux store reducer. You can access every prop in them and these will be sent via props in your connected component. For instance:
//redux
import { connect } from 'react-redux'
import styles from './Styles'
class HomeScreen extends React.Component {
constructor() {
super();
this.state = {
};
}
toggleOpenCircleModal = () => {
if(this.props.action === 'openModal') {
this.props.openModal();
} else {
this.props.closeModal();
}
}
render() {
const { action } = this.props; // this.props.action is coming from Redux Store
return (
<SafeAreaView>
{action} // this will be 'openModal'
</SafeAreaView>
);
}
}
function mapStateToProps(state) {
return { action: state.action } // this will be available in HomeScreen as props.action
}
function mapDispatchToProps(dispatch) {
return {
openModal: () =>
dispatch({
type: "OPEN_MODAL"
})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(HomeScreen)
You can read more on https://react-redux.js.org/using-react-redux/connect-mapstate.
The same goes for mapDispatchToProps. In your case, openModal will be available in props.openModal in your HomeScreen component. You can read more on https://react-redux.js.org/using-react-redux/connect-mapdispatch
Based on this, in your AddCircleModal component, you should be using props.action to evaluate if the modal should be visible. (props.action === 'openModal').
If you want to open or close your modal, you'll just need to call the openModal or closeModal dispatch call in your component. In HomeScreen component, in your function toggleOpenCircleModal, you will call openModal() or closeModal() depending on props.action === 'openModal'.
Lastly, you should be using just a boolean value to check for the modal visibility, instead of a string, if that's the only purpose for your reducer.
const initialState = false;
const modalToggle = (state = initialState, action) => {
switch (action.type) {
case "OPEN_MODAL":
return true;
case "CLOSE_MODAL":
return false;
default:
return state
}
}
export default modalToggle
I have a component, this component is nothing but a WebView.
I make a call to this component and I want a result back, through promises.
I have to make sure that after the WebView is loaded and running onNavigationStateChange, I have to return a Promise to return the result.
Main:
import * as React from 'react';
import { View } from 'react-native';
import ShortUrl from './ShortUrl';
export default class App extends React.Component {
componentDidMount() {
this.shortUrl
.init('https://www.cineblog.life/?trdownload=0&trid=24045&movie=0')
.then(uid => {
console.log('URL: ' + uid);
})
.catch(err => alert('error: ' + err));
}
render() {
return (
<View>
<ShortUrl
ref={r => (this.shortUrl = r)}
style={{ width: 0, height: 0, backgroundColor: '#000' }}
/>
</View>
);
}
}
ShortUrl:
import * as React from 'react';
import { View, WebView } from 'react-native';
export default class ShortUrl extends React.Component {
constructor() {
super();
this.state = {
initialUrl: '',
init: false,
//promise:
};
}
init(initialUrl) {
this.setState({ initialUrl, init: true });
return new Promise(async (resolve, reject) => {
resolve('OK');
});
}
onNavigationStateChange = navState => {
const { initialUrl } = this.state;
if (initialUrl !== navState.url) {
return new Promise(async (resolve, reject) => {
resolve(navState.url);
});
}
};
render() {
const { initialUrl, init } = this.state;
if (!init) return null;
return (
<View>
<WebView
source={{
uri: initialUrl,
}}
onNavigationStateChange={this.onNavigationStateChange}
style={{ flex: 1 }}
/>
</View>
);
}
}
Link expo: Here
I can't fully understand your question, but it seems you're simply looking for a function passed as prop to your child component. Once your promise is completed just call that function you have in prop and pass it a parameter, in order to handle it in the parent component.
In your Main:
[...]
<ShortUrl
ref={r => (this.shortUrl = r)}
style={{ width: 0, height: 0, backgroundColor: '#000' }}
handleResult={ resultArrivingFromChild => { // do what you want with the result }}
/>
In ShortUrl:
onNavigationStateChange = navState => {
[...] // do what you have to do
this.props.handleResult(resultYouWantToSendBack);
};
I am using expo, react-native, redux, react-navigation, and react-intl. Expo has this async Localization.getCurrentLocaleAsync() function to retrieve the locale asynchronously. I encountered problem propagating changes of locale and messages down to child components.
For example, if I set initial locale to "es" in "Root.js", when the Localization.getCurrentLocaleAsync() kick in and set the locale to "en", the updated messages was not reflected in the child component "Login.js". As such, the simulator throws a console.error: Missing message: "Login.login" for locale: "es", using default message as fallback while I updated the locale and message in the root.js state Here's my code:
root.js
import React from 'react';
import { Provider } from 'react-redux';
import { StyleSheet, Text, View, Alert } from 'react-native';
import { DangerZone } from 'expo';
import { IntlProvider, addLocaleData, injectIntl } from 'react-intl';
import { createBottomTabNavigator, createSwitchNavigator } from 'react-navigation';
import { PersistGate } from 'redux-persist/integration/react';
import AuthLoadingPage from './containers/authLoading';
import LoginPage from './containers/login';
import SignupPage from './containers/signup';
import HomePage from './containers/home';
import NotFoundPage from './containers/notFound';
import configureStore from './configureStore';
import en from 'react-intl/locale-data/en';
import es from 'react-intl/locale-data/es';
import localeData from './build/data.json';
addLocaleData([...en, ...es]);
const { Localization } = DangerZone;
const { persistor, store } = configureStore();
const AuthTab = createBottomTabNavigator({
login: { screen: LoginPage },
signup: { screen: SignupPage },
},{
navigationOptions: {
tabBarVisible: false,
},
lazyLoad: true,
});
const MainNavigator = createSwitchNavigator({
authLoading: AuthLoadingPage,
main: { screen: HomePage},
auth: AuthTab,
},{
initialRouteName: 'authLoading',
});
class Root extends React.Component {
constructor(p) {
super(p);
this.state = {
currentLocale: 'es',
messages: localeData['es'],
};
}
componentDidMount() {
Localization.getCurrentLocaleAsync()
.then(currentLocale => {
console.log("currentLocale is >>>", currentLocale);
this.setState({
currentLocale,
messages: localeData[currentLocale],
});
});
}
render() {
console.log("this.state.message???", this.state.messages);
return (
<IntlProvider
locale={this.state.currentLocale}
key={this.state.currentLocale}
messages={this.state.messages}
textComponent={Text}
>
<Provider store={store}>
<PersistGate
loading={<NotFoundPage />}
onBeforeLift={() => {}}
persistor={persistor}
>
<MainNavigator />
</PersistGate>
</Provider>
</IntlProvider>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
export default Root;
and "containers/Login.js":
import React, { Component } from 'react';
import { injectIntl, intlShape, FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import {
View,
Text,
TextInput,
Image,
Dimensions,
KeyboardAvoidingView,
StyleSheet,
Button,
TouchableOpacity
} from 'react-native';
import { FormLabel, FormInput } from 'react-native-elements';
import { authenticate } from '../modules/auth/actions';
const SCREEN_WIDTH = Dimensions.get('window').width;
class LoginPage extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: ''
};
}
handleSubmit(e) {
e.preventDefault();
const { email, password } = this.state;
const { navigation } = this.props;
this.props.dispatch(authenticate(email, password))
.then(() => {
navigation.navigate('main');
})
}
gotoSignup(e) {
e.preventDefault();
const { navigation } = this.props;
navigation.navigate('signup');
}
render() {
const { isAuthenticated, navigation } = this.props;
return (
<KeyboardAvoidingView behavior="padding" style={styles.container}>
<View style={styles.loginLogo}>
<FormattedMessage
id={ 'Login.login' }
defaultMessage={ 'Welcome to login screen!' }
/>
</View>
</KeyboardAvoidingView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
width: Dimensions.get('window').width,
},
loginLogo: {
flex:1,
},
loginForm: {
flex: 2,
},
loginFormContainer: {
flex: 1,
padding: 20,
},
input: {
height: 40,
backgroundColor: 'rgba(255,255,255, 0.8)',
paddingLeft: 10,
marginBottom: 15,
},
buttoncontainer: {
backgroundColor: '#23618C',
marginTop: 10,
paddingVertical: 15,
},
buttontext: {
textAlign: 'center',
color: '#fff',
fontWeight: 'bold',
},
});
function mapStateToProps(state) {
const { auth } = state;
const { loading, isAuthenticated } = auth;
return {
loading,
isAuthenticated
};
}
export default connect(mapStateToProps)(LoginPage);
you can also find the relavent code in github:
root.js: https://github.com/7seven7lst/mobile-client-new/blob/master/root.js
containers/Login.js: https://github.com/7seven7lst/mobile-client-new/blob/master/containers/login.js
Never mind. the above should be working. I got this id messed up. it should be "Login.Login" because that's what I have in the data.json file.
<FormattedMessage
id={ 'Login.login' }
defaultMessage={ 'Welcome to login screen!' }
/>
Very basic simple GET example for react-redux
I have a "MockAPI" which simulates a GET request to an API like so:
const dashboards = [
{
"Id":1,
"title":"Overview"
},
{
"Id":2,
"title":"Overview"
},
{
"Id":3,
"title":"Overview"
},
{
"Id":4,
"title":"Overview"
}
];
class DashboardApi {
static getAllDashboards() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(Object.assign([], dashboards));
}, delay);
});
}
}
I am trying to develop in a react-redux flow of dispatching an action via a button click and then updating the component via the redux store.
Here is my component code:
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import * as dashboardActions from '../../actions/dashboardActions';
class HomePage extends React.Component {
constructor(props) {
super(props);
this.loadDashboards = this.loadDashboards.bind(this);
}
loadDashboards() {
this.props.dispatch(dashboardActions.loadDashboards());
}
dashboardItem(dashboard, index) {
return <p key={index}>{dashboard.title}</p>;
}
render() {
return (
<div>
<h1>
Hello World!
<button onClick={this.loadDashboards}>load</button>
</h1>
{this.props.dashboards.map(this.dashboardItem)}
</div>
);
}
}
HomePage.propTypes = {
dashboards: PropTypes.array.isRequired,
dispatch: PropTypes.func.isRequired
};
function mapStateToProps(state) {
return {
dashboards: state.dashboards
};
}
export default connect(mapStateToProps)(HomePage);
And here is my dashboardActions.js:
import * as types from './actionTypes';
import dashboardApi from '../mockApi/mockDashboardApi';
export function loadDashboardsSuccess(dashboards) {
return { type: types.LOAD_DASHBOARDS_SUCCESS, dashboards };
}
export function loadDashboards() {
return dispatch => {
return dashboardApi
.getAllDashboards()
.then(dashboards => {
dispatch(loadDashboardsSuccess(dashboards));
});
};
}
And here is my reducer:
import initialState from './initialState';
import * as types from '../actions/actionTypes';
export default function dashboardReducer(state = initialState.dashboards, action) {
switch(action.types) {
case types.LOAD_DASHBOARDS_SUCCESS:
return action.dashboards;
default:
return state;
}
}
I am trying to get the onClick to load in the dashboards array and to render as <p> tags simply displaying the title value. Unfortunately it is not happening.
I see that the LOAD_DASHBOARDS_SUCCESS action is getting loaded, but I see that the dashboards property in the store is still an empty array instead of showing the returned data...
What am I missing here?
You've got a typo in your reducer. switch(action.types) should be switch(action.type) with no 's'