Hye guys.. im trying to use react-navigation and firebase in my project.
Im using this awesome boilerplate :-
https://github.com/jhen0409/react-native-boilerplate
in my navigator.js
import { StackNavigator } from 'react-navigation';
import Home from './containers/Home';
import MainScreen from './containers/MainScreen';
import HelpScreen from './containers/HelpScreen';
const AppNavigator = new StackNavigator(
{
Home: { screen: Home },
MainScreen: { screen: MainScreen },
HelpScreen: { screen: HelpScreen }
},
{
headerMode: 'screen'
},
);
export default AppNavigator;
and then in my landing screen which is Home.js
#firebaseConnect()
#connect(
state => ({
nav: state.nav.routes
}),
dispatch => bindActionCreators(userActions, dispatch),
)
export default class Home extends Component {
componentDidMount() {
this.props.firebase.auth().onAuthStateChanged( user => {
if(user) {
//check route stack in redux store
if(this.props.nav[this.props.nav.length-1].routeName !== 'MainScreen') {
this.props.navigation.navigate('MainScreen');
}
this.props.firebase.updateProfile({ lastLogin: new Date() });
user.getIdToken().then( t => this.props.userToken(t) );
} else {
this.props.firebase.auth().signInAnonymously()
.then((user) => {
console.log('user successfully sign in anonymously', user);
// Insert user record to firebase
this.props.firebase.updateProfile(
{
name: 'Anonymous'
}
)
})
.catch(
(error) => {
console.log('error ', error)
})
}
})
}
render() {
return (
<View />
);
}
}
and inside my MainScreen.js
#firebaseConnect(['/helpDetails'])
#connect(
(state, props) => {
return({
})
}
)
export default class MainScreen extends Component {
logout = () => {
this.props.navigation.goBack();
this.props.firebase.logout();
console.log('logout!');
}
render() {
return (
<View style={{ flex: 1 }}>
<TouchableOpacity onPress={() => this.logout()}>
<Text>LOG OUT</Text>
</TouchableOpacity/>
</View>
)
}
}
everything is going fine when user open the apps.. but it start to give this red screen when I click the logout.. if I change firebaseConnect inside Mainscreen from
#firebaseConnect(['/helpDetails'])
TO
#firebaseConnect([])
then everything is working fine..
can anyone help me what im doing wrong here? thanks!
I think this is not a problem of you, but of the library. I have the same issue. Thank god this is only happening while in developer mode (in release everthing works fine).
When I try it without devtools, it works. In my opinion react-redux-firebase is doing some weird stuff when logging out and creates (maybe just for one second) a circular JSON-structure. In JavaScript itself this isn't a big problem, but when you want to stringify it (which is done to display it in your devtools), then the circular structure cannot be converted to a String. Hope to see a fix for that soon from the devs.
Related Issue: Github Issue react-redux-firebase
Related
I'm building navigation to my App,
and I'm trying to give the navigation certain conditions on which to navigate around.
for example:haveFilledForm ? go to screen x : go to screen y
Those conditions are base on currentUser object which i get from Firebase Live DataBase
I configure a database.js with functions to use the live database, this is my Get User Function From Database:
export const getUser = async (uid) => {
firebase
.database()
.ref("users/" + uid)
.once("value")
.then((snap) => {
console.log(snap);
return snap;
});
};
Now, I manage different navigators from one main file called AppNavigator.js
In this file I'm try to get user Information and redirect user according to his props from the livedatabase which i get using firebase.auth()
Here is this page:
import * as React from "react";
import { NavigationContainer } from "#react-navigation/native";
import { AuthStack } from "./AuthStack";
import { TabNavigator } from "./TabNavigator";
import PersonalInfo from "../screens/Auth/PersonalInfo";
import firebase from "../util/firebase";
import { AppLoading } from "expo";
import { getUser } from "../util/database";
export default class AppNavigator extends React.Component {
constructor(props) {
super(props);
this.state = {
user: "",
isLoading: true,
isFilled: false,
userInfo: {},
};
}
componentDidMount() {
firebase.auth().onAuthStateChanged(async (user) => {
if (user) {
console.log("logged in " + JSON.stringify(user.uid));
this.setState({ user });
} else {
console.log("not logged in");
this.setState({ user: "" });
this.setState({ isLoading: false });
}
getUser(this.state.user.uid)
.then((usr) => {
console.log(usr + " usr");
this.setState({ userInfo: usr });
this.setState({ isLoading: false });
})
.then(console.log("Imlast"));
});
}
render() {
if (this.state.isLoading) {
return <AppLoading />;
} else {
return (
<NavigationContainer>
{this.state.user ? (
this.state.userInfo ? (
<TabNavigator />
) : (
<PersonalInfo />
)
) : (
<AuthStack />
)}
</NavigationContainer>
);
}
}
}
the console.log output from this page is:
logged in "jmC6C5quEFSY0cFBfT8dqJe5E8a2"
Imlast
undefined usr
Object {
"admin": false,
"credit": 0,
"injuries": "",
"isProfileFilled": true,
"phone": "",
}
Now I can't seem to understand why Imlast is printed before the user object,
I understand that it got something to do with Event Loop and the fact that firebase is async.
So what is the right way to achieve this?
Eventually my end goal is to redirect user based on his "isProfileFilled" value from database
The reason Im last is printed before undefined usr is because .then accepts a callback function but you pass it a statement. So when the js interpreter goes over your code it executes the console.log immediately instead of waiting for it to be invoked when the promise is resolved. This how you can fix it:
getUser(this.state.user.uid)
.then((usr) => {
console.log(usr + " usr");
this.setState({ userInfo: usr });
this.setState({ isLoading: false });
})
.then(() => console.log("Imlast"));
//Instead of .then(console.log("Imlast"));
Changing state in react is asynchronous! Which means that when you call setState in the next line this.state isn't guaranteed to equal the new state.
I suggest you use the user object you receive in the callback like this: getUser(user.uid). In addition to that it seems redundant to save the user twice in the component state(user and userInfo).
Instead of saving an empty string for a user to represent there is no user, just initialize it as null in the ctor.
Lastly to show different components base on isProfileFilled you can do it like this:
render() {
const { user, isLoading } = this.state
if (isLoading) {
return <AppLoading />;
} else if(!user) {
return <AuthStack />
} else {
return <NavigationContainer>
{user.isProfileFilled ? <TabNavigator /> : <PersonalInfo />}
</NavigationContainer>
}
}
I want to implement dark mode and black mode in my app and the way I have it is that the user toggles on dark/black mode from one class in which I want the state to be updated to all the classes, the toggle class is as followed:
AppearanceToggle Class
state = {
BlackModeValue: null,
DarkModeValue: null
};
componentDidMount = () => {
AsyncStorage.getItem('DarkModeValue').then(value => this.setState({ DarkModeValue: JSON.parse(value) }));
AsyncStorage.getItem('BlackModeValue').then(value => this.setState({ BlackModeValue: JSON.parse(value) }));
};
//AsyncStorage.setItem .........
render() {
return (
<ScrollView style={[ styles.View , this.state.DarkModeValue ? darkmode.ASView : null || this.state.BlackModeValue ? blackmode.ASView : null ]}>
<SettingsList borderColor='#c8c7cc' defaultItemSize={50}>
<SettingsList.Item
hasSwitch={true}
switchState={this.state.DarkModeValue}
switchOnValueChange={//Goes to asyncStorage.setItem method}
title='Dark Mode'
/>
<SettingsList.Item
hasSwitch={true}
switchState={this.state.BlackModeValue}
switchOnValueChange={//Goes to asyncStorage.setItem method}
title='Black Mode'
/>
</SettingsList>
</ScrollView>
);
}
}
And then in the class (which is SettingsScreen.js, this is the screen that navigates to AppearanceToggle ) that I want to .getItem and change the state is as followed:
state = {
switchValue: false,
rated: false,
DarkModeValue:null,
BlackModeValue:null,
};
componentDidMount = () => {
AsyncStorage.getItem('DarkModeValue').then(value => this.setState({ DarkModeValue: JSON.parse(value) }));
AsyncStorage.getItem('BlackModeValue').then(value => this.setState({ BlackModeValue: JSON.parse(value) }));
};
render() {
return (
<ScrollView style={[ styles.View , this.state.DarkModeValue ? styles.DMView : null || this.state.BlackModeValue ? styles.BMView : null ]}>
..........
</ScrollView>
The problem I have is that when I change the switch, it affects the AppearanceToggleScreen Class instantly but not the SettingsScreen UNLESS I refresh the app. Is there a way to do it so all of them get affected instantly?
Perhaps the best way to propagate it is to listen for the changes in your AppComponent using Context or root component. e.g.
So you would create a theme context like :
export const themes = {
blackMode: {
foreground: '#000000',
background: '#eeeeee',
},
darkMode: {
foreground: '#2f4f4ff',
background: '#222222',
},
};
export const ThemeContext = React.createContext(
themes.darkMode // default value
)
;
Your AppearanceToggle class would have something like :
import {ThemeContext} from './theme-context';
class ThemedButton extends Component {
render() {
let props = this.props;
let theme = this.context;
return (
<button
{...props}
style={{backgroundColor: theme.background}}
/>
);
}
}
ThemedButton.contextType = ThemeContext;
export default ThemedButton;
And then your AppComponent could be
import {ThemeContext, themes} from './theme-context';
import ThemedButton from './themed-button';
function Toolbar(props) {
// Render your customized toolbar here and bind the changeTheme function to it
}
class App extends Component {
constructor(props) {
super(props);
};
componentDidMount = () => {
AsyncStorage.getItem('selectedTheme').then(value => this.setState({ selectedTheme: JSON.parse(value) }));
};
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.darkMode
? themes.blackMode
: themes.darkMode,
}));
};
}
render() {
// The ThemedButton button inside the ThemeProvider
// uses the theme from state while the one outside uses
// the default dark theme
return (
<Page>
<ThemeContext.Provider value={this.state.theme}>
<Toolbar changeTheme={this.toggleTheme} />
</ThemeContext.Provider>
<Section>
<ThemedButton />
</Section>
</Page>
);
}
}
For more read
The thing is that in the AppearanceToggleScreen you're changing the state, therefore the component is rerendered (with the new theme), but because the SettingsScreen is already in the navigation stack (because that's where you're navigating from) the componentDidMount is not executing again.
Now, maybe you want to use the Context API to access globally to the values, or do something like this.
I've been building an app in React Native Expo. First, I incorporated Facebook Login simply by copying and pasting the login async code into Login.js and added this.login() to componentWillMount. This worked - With the Facebook login popup showing up as app loads. I was able to log into my FB account with a success message.
However, as soon as I tried to incorporate Firebase, particularly somewhere between transferring code between my Home.js page and the Login.js page, I started getting this white screen to appear on page load.
There are no errors in a terminal; except a message that FacebookAppID and facebookDisplayName do not belong in app.json.
I tried adding a different background color (black) in CSS, which works, but still, there is no content.
Removing FacebookAppID and facebookDisplayName from app.json, which did nothing.
Updating my App Key to the correct one (I was missing the last number).
Restarted the terminal, expo web terminal x code and metro builder several times.
Updated my code so that every file in my Screens directory has { connect } & { login } imports as well as functionMapStateToProps and export default connect statements at bottom.
I tried changing a tab in TabNavigator.js to Login page, and using "Login" as the initialRouteName, but got an error that Login.js isn't a React component.
The first page that should show up before any other is the Facebook login...So it would seem the issue is there.
App.js
import React from 'react';
import Login from './screens/Login';
import reducers from './redux/reducers';
import thunkMiddleware from 'redux-thunk';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
const middleware = applyMiddleware(thunkMiddleware);
const store = createStore(reducers, middleware);
export default class App extends React.Component {
render() {
return (
<Provider store={store}>
<Login/>
</Provider>
);
}
}
------ end of App.js ------------
Login.js
import React from 'react';
import styles from '../styles'
import RootNavigator from '../navigation/RootNavigator';
import { connect } from 'react-redux';
import { login } from '../redux/actions';
import * as firebase from 'firebase';
import firebaseConfig from '../config/firebase.js';
firebase.initializeApp(firebaseConfig)
import {
Text,
View,
TouchableOpacity
} from 'react-native';
class Login extends React.Component
state = {}
componentWillMount() {
firebase.auth().onAuthStateChanged((user) => {
if (user != null) {
this.props.dispatch(login(true))
console.log("We are authenticated now!" + JSON.stringify(user));
}
});
}
login = async () => {
const { type, token } = await Expo.Facebook.logInWithReadPermissionsAsync('YourAppKeyGoesHere', {
permissions: ['public_profile'],
});
if (type === 'success') {
// Build Firebase credential with the Facebook access token.
const credential = await firebase.auth.FacebookAuthProvider.credential(token);
// Sign in with credential from the Facebook user.
firebase.auth().signInWithCredential(credential).catch((error) => {
// Handle Errors here.
Alert.alert("Try Again")
});
}
}
render() {
if(this.props.loggedIn){
return (
<RootNavigator/>
)
} else {
return (
<View style={styles.container}>
<TouchableOpacity onPress={this.login.bind(this)}>
<Text>{this.props.loggedIn}</Text>
</TouchableOpacity>
</View>
)
}
}
}
function mapStateToProps(state) {
return {
loggedIn: state.loggedIn
};
}
export default connect(mapStateToProps)(Login);
---------end of Login.js ----------
Home.js
import React from 'react';
import styles from '../styles';
import { connect } from 'react-redux';
import { login } from '../redux/actions';
import {
Text,
View,
Alert
} from 'react-native';
class Home extends React.Component {
state = {}
componentWillMount() {
}
render() {
return (
<View>
<Text>Home</Text>
</View>
)
}
}
function mapStateToProps(state) {
return {
loggedIn: state.loggedIn
};
}
export default connect(mapStateToProps)(Home);
-----end of Home.js ------
redux folder
actions.js
export function login(){
return function(dispatch){
dispatch({ type: 'LOGIN', payload: input });
}
}
----end of actions.js ----
reducers.js
export default reducers = (state = {
loggedIn: false,
}, action) => {
switch (action.type) {
case 'LOGIN': {
return { ...state, loggedIn: action.payload }
}
}
return state;
}
------end of reducers.js ------
-----end of redux folder ------
-----navigation folder (react navigation) -------
---RootNavigator.js---
import React from 'react';
import TabNavigator from './TabNavigator';
import {
createDrawerNavigator,
createStackNavigator,
createBottomTabNavigator,
createAppContainer,
} from 'react-navigation';
const AppNavigator = createStackNavigator(
{
Main: {
screen: TabNavigator,
},
}
);
const AppContainer = createAppContainer(AppNavigator);
export default class RootNavigator extends React.Component {
render() {
return <AppContainer/>;
}
}
----end of RootNavigator.js-----
----TabNavigator.js----
import React from 'react';
import Home from '../screens/Home';
import Profile from '../screens/Profile';
import Matches from '../screens/Matches';
import {
createDrawerNavigator,
createStackNavigator,
createBottomTabNavigator,
createAppContainer,
createMaterialTopTabNavigator,
} from 'react-navigation';
export default createBottomTabNavigator(
{
Profile: {
screen: Profile,
navigationOptions: {
tabBarLabel: 'Profile',
},
},
Home: {
screen: Home,
navigationOptions: {
tabBarLabel: 'Home',
}
},
Matches: {
screen: Matches,
navigationOptions: {
tabBarLabel: 'Matches',
},
},
},
{
navigationOptions: {
header: null
},
tabBarPosition: 'top',
initialRouteName: 'Home',
animationEnabled: true,
swipeEnabled: true,
tabBarOptions: {
style: {
height: 75,
backgroundColor: 'blue'
},
}
}
);
-----end of TabNavigator----
Have you tried remote js Debugging?
What you can do is, Debugg JS remotely.
https://developers.google.com/web/tools/chrome-devtools/remote-debugging/
try to console.log("hi"); when your first component of your app mounts.
Try to add it in login page when the login component mounts.
That will help you debug unseen error which gets listed in the js debugger.
Just check those errors and follow up!
You're good to go!
I was also getting splash logo white screen, tired possible solution nothing works out, at last I have remove node_module and yarn.lock. then reinstall and update expo
follows cmd:-
$ npm install
$ yarn add expo
$ expo update
try this , works for me.
!!enjoy!!
As the other answer suggests, once you've done console.log to see the component is actually loading, then for me the issue was I couldn't actually see the content.
My solution was to wrap my content with a <View> to align the content in the middle of the page.
I understand your question is more complex than that, but hopefully, my answer might be able to help other people.
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
}}>
<Text>Can you see this?</Text>
</View>
in my case,
style = {{ borderColor : #fff }}
my mistake is exceptin ' at borderColor value...
fix change to
style = {{ borderColor : '#fff' }}
Some components such as useState was imported from wrong url, I changed it and imported it from react and fixed it
I am trying to log in with a phone number in my app with firebase but I am facing issue with the login process. I'm not able to login with a phone number in firebase but if I register with a phone number and redirect to the homepage it's working properly. I am using the same method to login, but I got the issue like TypeError: Cannot read property 'uid' of null but I an successfully getting all the console values. I don't know what is being the issue here. But that error is displaying in 3 times repeatedly,
Here is my code:
renderLoginButton() {
if (this.props.loading) {
return (
<Spinner size="large" />
);
}
return (
<Button
style={{ alignSelf: 'flex-start' }}
onPress={this.onLoginBtnClicked.bind(this)}
>
Login
</Button>
);
}
onLoginBtnClicked() {
const { contact, password } = this.props;
const error = Validator('password', password) || Validator('contact', contact);
if (error !== null) {
Alert.alert(error);
} else {
console.log('else');
// this.props.loginUser({ contact, password});
const mobileNo = '+91'+contact;
firebase.auth().signInWithPhoneNumber(mobileNo)
.then(confirmResult =>
console.log(confirmResult),
curr = firebase.auth(),
console.log("curr"+JSON.stringify(curr)),
this.setState({ data: curr}),
NavigationService.navigate('Home')
)
.catch(error => console(error.message) );
}
}
CustomDrawerComponent.js
import React, { Component } from 'react';
import { View, Image, Text } from 'react-native';
import { DrawerItems } from 'react-navigation';
import { connect } from 'react-redux';
import { fetchUserDetails } from '../actions';
class CustomDrawerContentComponent extends Component {
state = {
uri: '',
isfailed: ''
}
componentWillMount() {
this.props.fetchUserDetails();
}
componentWillReceiveProps(nextProps) {
let uri = '';
if (nextProps.ProfilePic !== '') {
uri = nextProps.ProfilePic;
this.setState({ uri, isfailed: false });
} else {
uri = '../images/ic_person_24px.png';
this.setState({ uri, isfailed: true });
}
this.setState({ uri });
}
renderProfileImage() {
if (!this.state.isfailed) {
return (
<Image
style={styles.profileImageStyle}
source={{ uri: (this.state.uri) }}
/>
);
}
return (
<Image
style={styles.profileImageStyle}
source={require('../images/ic_person_24px.png')}
/>
);
}
render() {
console.log('Profile Pic :: ', this.props.ProfilePic);
return (
<View style={styles.container}>
{this.renderProfileImage()}
<Text style={styles.textStyle}>
{this.props.name} - {this.props.category}
</Text>
<DrawerItems {...this.props} />
</View>
);
}
}
const styles = {
container: {
flex: 1,
paddingLeft: 10
},
textStyle: {
fontSize: 14,
textAlign: 'left',
color: '#000000'
},
profileImageStyle: {
alignSelf: 'flex-start',
marginTop: 16,
padding: 10,
width: 40,
height: 40,
borderRadius: 75
}
};
const mapStateToProps = state => {
const { userprofile } = state;
return userprofile;
};
export default connect(mapStateToProps, { fetchUserDetails })(CustomDrawerContentComponent);
callStack:
Why does the user return as undefined (or even null)?
You know there’s a logged in user, you just logged in, heck, you can even see the user object in chrome dev tools.
Then why is it still returning undefined? There’s a straight answer to it.
You’re fetching the user object BEFORE that object is ready to be used.
Now, this can happen because of several different reasons, but if you follow this 2 "rules" you won’t see that error again.
Rule #1: Move it out of the constructor()
When you have something like:
constructor(){
this.userId = firebase.auth().currentUser.uid
}
Over half of the time that page loads, the constructor is going to try to get the user before the user is ready, the app is blocking it because the page isn’t fully loaded, so you’re going to be trying to access uid of a property that just isn’t there yet.
When you get your page fully loaded, you can now call to get the currentUser.uid
Rule #2: Make it an Observable
There’s another approach you can take, that previous Firebase call we just made: firebase.auth().currentUser is synchronous. We can make it asynchronous by subscribing to the auth observable instead.
/**
* When the App component mounts, we listen for any authentication
* state changes in Firebase.
* Once subscribed, the 'user' parameter will either be null
* (logged out) or an Object (logged in)
*/
componentDidMount() {
this.authSubscription = firebase.auth().onAuthStateChanged((user) => {
this.setState({
loading: false,
user,
});
});
}
/**
* Don't forget to stop listening for authentication state changes
* when the component unmounts.
*/
componentWillUnmount() {
this.authSubscription();
}
render() {
// The application is initialising
if (this.state.loading) return null;
// The user is an Object, so they're logged in
if (this.state.user) return <LoggedIn />;
// The user is null, so they're logged out
return <LoggedOut />;
}
}
Source article: Why does Firebase return undefined when fetching the uid?
A good tutorial for React Native will be here: Getting started with Firebase Authentication on React Native
Since, your code did not show much, I hope you make an update to your question to show more code, so I might be able to look through.
Newbie here trying to learn some Redux.
GOAL: to get a button to click and login/logout, updating the store as true/false status whichever way.
const store = createStore(myReducer)
Created my store, passing in my reducer.
This has a default state of logged out. And returns the opposite, whenever the button is clicked.
I know this action works through debugging.
function myReducer(state = { isLoggedIn: false }, action) {
switch (action.type) {
case 'TOGGLE':
return {
isLoggedIn: !state.isLoggedIn
}
default:
return state
}
}
The problem starts here - when i try to access the store.getState() data.
class Main extends React.Component {
render() {
return (
<div>
<h1>Login Status: { state.isLoggedIn }</h1>
<button onClick={this.props.login}>Login</button>
</div>
)
}
}
const render = () => {
ReactDOM.render(<Main status={store.getState().isLoggedIn} login={() => store.dispatch({ type: 'TOGGLE' })}/>, document.getElementById('root'));
}
store.subscribe(render);
render();
I've tried store.getState().isLoggedIn & store.getState() & this.props.status and then assigning the store.getState().isLoggedIn in the Main component - but nothing works.
Can anyone tell me where i'm going wrong?
You don't directly access the store using getState to find data. The Redux docs explain the process in-depth, but basically you'll connect each component to the Redux store using connect method of the react-redux package.
Here's an example of how this could work for your above component:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import Main from '../components/Main'
class MainContainer extends Component {
render() {
return <Main {...this.props} />
}
}
const mapStateToProps = state => ({
isLoggedIn: state.isLoggedIn,
})
const mapDispatchToProps = dispatch => ({
login() {
dispatch({type: 'TOGGLE'})
},
})
MainContainer = connect(
mapStateToProps,
mapDispatchToProps,
)(MainContainer)
export default MainContainer
You would then want to render the MainContainer in place of the Main component. The container will pass down isLoggedIn and login as props to Main when it renders it.