understanding Event Loop using Firebase & React Native - firebase

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>
}
}

Related

Next.js role based access to component

I'm trying to restrict access to a admin area based on a int flag called isAdmin that is set to either 0 or 1 in my users table.
In the admin component I've made a function to fetch an API route that returns a unique user based on email, which will allow me to pass this parameter from the session to the API route - but it never returns true value
This is the code for the lookup function and how I restrict access in the component
export const getServerSideProps: GetServerSideProps = async () => {
const dashboards = await prisma.dashboard.findMany({
orderBy: {
id: "asc",
}
})
return {
props: JSON.parse(JSON.stringify({ dashboards })),
}
}
async function checkAdminUser(email: string) {
try {
const result = await fetch(`/api/user/${email}`, {
method: "GET",
})
const user = await result.json()
if (user.isAdmin == 1) {
return true
} else {
return false
}
} catch (error) {
console.error(error)
}
}
const Dashboard: React.FC<Props> = (props) => {
const { data: session, status } = useSession()
if (!session || !checkAdminUser(session.user?.email)) {
return (
<Layout>
<AccessDenied />
</Layout>
)
}
return (
<Layout>
..Layout code
</Layout>
)
}
I've also tried the checkAdminUser function as a Promise without success. The API route has been checked for valid output
"{"id":1,"image":null,"name":null,"email":"censoredfor#crawlers.com","emailVerified":null,"isAdmin":1,"createdAt":"2022-09-21T07:52:20.263Z","updatedAt":"2022-09-21T10:22:39.024Z"}"
Any tips to get me rolling would be greatly appreciated!
This answer assumes that console.log(user) gives you: {"id":1,"image":null,"name":null,"email":"censoredfor#crawlers.com","emailVerified":null,"isAdmin":1,"createdAt":"2022-09-21T07:52:20.263Z","updatedAt":"2022-09-21T10:22:39.024Z"}
Then you should better handle how you check if the user is an Admin. checkAdminUser returns a promise that is not resolved when you're checking for the value. A better solution would be to use react state to manage access to a specific component:
const Dashboard: React.FC<Props> = (props) => {
const { data: session, status } = useSession()
const [isAdmin, setAdmin] = useState(false);
const checkAdminUser = useCallback(async () => {
try {
const result = await fetch(`/api/user/${session?.user?.email}`, {
method: "GET",
})
const user = await result.json()
if (user.isAdmin == 1) {
setAdmin(true)
} else {
setAdmin(false)
}
} catch (error) {
console.error(error)
}
},[session?.user?.email])
useEffect(() => {
checkAdminUser()
},[checkAdminUser])
if (!session || !isAdmin) {
return (
<Layout>
<AccessDenied />
</Layout>
)
}
return (
<Layout>
..Layout code
</Layout>
)
}
Don't forget to: import {useCallback, useEffect, useState} from 'react'

How can I attach word/pdf files to React Native gifted chat?

I would like to be able to send word doc/pdf files via messaging in my react native app using react native gifted chat. I have had a look at a few links which suggests using the renderActions() function in react-native-gifted-chat but it does not specify how I can implement this. Do you know how I can implement this function? Would I need to import a package like document picker or file picker in the function? If so, how can I use this? I'm fairly new to react native. Can someone please help here?
Here is what I have so far in my renderActions() method:
renderActions() {
return(
<Actions
{...props}
options={{
['Document']: async (props) => {
try {
const result = await DocumentPicker.pick({
type: [DocumentPicker.types.doc || DocumentPicker.types.docx || DocumentPicker.types.pdf],
});
console.log("resulting file: "+result);
console.log("string result? "+JSON.stringify(result));
} catch(e){
if(DocumentPicker.isCancel(e)){
console.log("User cancelled!")
} else {
throw e;
}
}
},
['Cancel']: (props) => {console.log("cancel")}
}}
icon={() => (
<Ionicons
name={'add'}
size={28}
color={'#0077ff'}
style={{left:0, bottom:0}}
/>
)}
onSend={args => console.log(args)}
/>
)
}
Which produces:
I have managed to get the file object. Does anyone know how I can append this doc file object to the messages in gifted chat once selected? Can someone please help? How can I display in the chat box and then send the file?
Thanks.
The link https://github.com/FaridSafi/react-native-gifted-chat/issues/2111 mentions to to add parameters to the message object. For example you have this message object:
const newMessage = {
_id: data.send_at,
text: data.messagetext,
createdAt: data.send_at,
(...),
file_type: data?.file_type,
file_id: data?.file_id,
}
Then render a custom view:
const renderCustomView = (props) => {
if (props?.currentMessage?.file_type) {
(...)
}
else {
(...)
}
}
Can someone please help on where I would need to create the messages object as well as what I would need to put inside the renderCustomView function? I am really not too sure on what needs to be done.
function renderActions(props) {
let selectFile = async () => {
//Opening Document Picker to select one file
try {
const res = await DocumentPicker.pick({
//Provide which type of file you want user to pick
type: [DocumentPicker.types.pdf],
//There can me more options as well
// DocumentPicker.types.allFiles
// DocumentPicker.types.images
// DocumentPicker.types.plainText
// DocumentPicker.types.audio
// DocumentPicker.types.pdf
});
//Printing the log realted to the file
console.log('res : ' + JSON.stringify(res));
props.onSend({pdf:res.uri,file_type:'pdf'});
//Setting the state to show single file attributes
singleFile = res;
// setSingleFile(res);
} catch (err) {
singleFile = null;
// setSingleFile(null);
//Handling any exception (If any)
if (DocumentPicker.isCancel(err)) {
//If user canceled the document selection
alert('Canceled from single doc picker');
} else {
//For Unknown Error
alert('Unknown Error: ' + JSON.stringify(err));
throw err;
}
}
};
const handlePicker = () => {
// console.log('edit');
ImagePicker.showImagePicker({}, (response) => {
// console.log('Response = ', response);
if (response.didCancel) {
console.log('User cancelled image picker');
} else if (response.error) {
console.log('ImagePicker Error: ', response.error);
} else if (response.customButton) {
console.log('User tapped custom button: ', response.customButton);
} else {
setAvatar({uri: response.uri});
console.log(response.uri);
props.onSend({image:response.uri});
// onSend([{"_id": "f3fda0e8-d860-46ef-ac72-0c02b8ea7ca9", "createdAt": new Date(), "image": response.uri, "user": {"_id": 1}}])
return response.uri
// here we can call a API to upload image on server
}
return avatar;
});
};
return (
<Actions
{...props}
options={{
['Send Image']: () => handlePicker(),
['Send Files']: () => selectFile(),
}}
icon={() => (
<Icon name='attachment' size={28} />
)}
// onSend={onSend}
/>
)}
in custom view :
export default class CustomView extends React.Component {
renderPdf() {
return (
<TouchableOpacity style=
{[styles.container,this.props.containerStyle]} >
<Image
{...this.props.imageProps}
style={[styles.image, this.props.imageStyle]}
source ={{
uri:""
}}
/>
</TouchableOpacity>
);
}
render() {
if (this.props.currentMessage.file_type == 'pdf') {
return this.renderPdf();
} else if (this.props.currentMessage.template &&
this.props.currentMessage.template != 'none') {
return this.renderHtml();
}
return null;
}
}

Parent re-rendering after next state change

I am new to Redux and I appear to be having an issue. Once my action has been dispatched it is successful however the parent component does not get the updated state until another state change is made. If I click login then delete a character in the input field the state change is then triggered showing me the Menu. Any help/pointers are much appreciated, thanks.
Main (Parent):
import React, { Component } from 'react'
import { connect } from 'react-redux'
import Login from '../login'
import Menu from '../menu'
type Props = { token: string }
class Main extends Component<Props, {}> {
render() {
const { token } = this.props;
if (!token) {
return (
<Login />
)
}
return (
<Menu />
)
}
}
const mapStateToProps = (state) => ({
token: state.session.token,
})
export default connect(
mapStateToProps,
null,
)(Main)
Login (Child):
import React from 'react'
import { connect } from 'react-redux'
import { login } from '../../redux/session/session.actions'
import { View, StyleSheet } from 'react-native'
import { Button, FormLabel, FormInput, FormValidationMessage } from 'react-native-elements'
import styled from 'styled-components/native'
const Container = styled(View)`
flex: 1;
flex-direction: column;
justify-content: center;
align-items: center;
`
const Wrapper = styled(View)`
width: 300;
`
type Props = { login: Function, error: string, loading: boolean };
type State = { email: string, password: string };
class Login extends React.PureComponent<Props, State> {
constructor(props) {
super(props);
this.state = {
email: null,
password: null,
}
}
render() {
console.log('props', this.props);
console.log('state', this.state);
const { loading, error } = this.props;
return (
<Container>
<Wrapper>
<FormValidationMessage>{loading ? 'Loading...' : null}</FormValidationMessage>
<FormValidationMessage>{error ? 'Unable to login, please try again.' : null}</FormValidationMessage>
<FormLabel>Email:</FormLabel>
<FormInput onChangeText={text => this.setState({ email: text })} />
<FormLabel>Password:</FormLabel>
<FormInput secureTextEntry onChangeText={password => this.setState({ password })} />
<Button title='Login' onPress={this.login} />
</Wrapper>
</Container>
)
}
login = () => {
this.props.login(this.state.email, this.state.password);
}
}
const mapStateToProps = (state) => {
console.log(state);
return {
error: state.session.error,
loading: state.session.loading
}
}
const mapDispatchToProps = ({
login
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(Login);
Reducer:
import {
LOGGING_IN,
LOGIN_SUCCESS,
LOGIN_FAILED
} from './session.types'
const initialState = {
loading: null,
error: null,
token: null,
}
export default (state = initialState, { type, payload }) => {
switch (type) {
case LOGGING_IN:
return {
...state,
loading: true
}
case LOGIN_SUCCESS:
return {
...state,
loading: false,
error: null,
token: payload.token
}
case LOGIN_FAILED:
return {
...state,
loading: false,
error: payload.error
}
default:
return state
}
}
Actions:
import { API_URL } from '../../../app-env'
import axios from 'axios'
import {
LOGGING_IN,
LOGIN_SUCCESS,
LOGIN_FAILED
} from './session.types'
export const login = (email, password) => (
async dispatch => {
console.log('here');
dispatch(loggingIn());
await axios.post(`${API_URL}/login`, {
email,
password
}).then(res => {
dispatch(loginSuccess(res.data.token))
}).catch(err => {
dispatch(loginFailed('Unable to login.'))
})
}
)
export const loggingIn = () => ({
type: LOGGING_IN,
})
export const loginSuccess = (token) => ({
type: LOGIN_SUCCESS,
payload: {
token
}
})
export const loginFailed = (error) => ({
type: LOGIN_FAILED,
payload: {
error
}
})
Since your problem is about Menu not render and Menu is under Main. So, we can ask the question what condition Main component not re-render. Luckily your example Main only depend on solely one props and no state. -I'll say your problem lies on props.token.- Since you initialize your token as null, I'll assume it hold object type. In that case, you need to make sure the token need to be a new object (new reference) else no re-render because react-redux connect by default will check the props changes before trigger the component underneath it.
EDIT: You mentioned the Menu not showing and the token is string, I can think of another reason Main not render is because connect is not trigger. You probably need to check the root of the store and make sure it has the new reference as your code only showing the reducer update state.session but not the state itself.

TypeError: Cannot read property 'uid' of null

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.

React props using Meteor Apollo

I am playing with the Meteor Apollo demo repo.
I am having difficulty passing variables down to children with React. I am getting an error
imports/ui/Container.jsx:10:6: Unexpected token (10:6)
The below code is the Container.jsx component:
import React from 'react';
import { Accounts } from 'meteor/std:accounts-ui';
class Container extends React.Component {
render() {
let userId = this.props.userId;
let currentUser = this.props.currentUser;
}
return (
<Accounts.ui.LoginForm />
{ userId ? (
<div>
<pre>{JSON.stringify(currentUser, null, 2)}</pre>
<button onClick={() => currentUser.refetch()}>Refetch!</button>
</div>
) : 'Please log in!' }
);
}
}
It is passed props via the Meteor Apollo data system (I have omitted some imports at the top):
const App = ({ userId, currentUser }) => {
return (
<div>
<Sidebar />
<Header />
<Container userId={userId} currentUser={currentUser} />
</div>
)
}
// This container brings in Apollo GraphQL data
const AppWithData = connect({
mapQueriesToProps({ ownProps }) {
if (ownProps.userId) {
return {
currentUser: {
query: `
query getUserData ($id: String!) {
user(id: $id) {
emails {
address
verified
}
randomString
}
}
`,
variables: {
id: ownProps.userId,
},
},
};
}
},
})(App);
// This container brings in Tracker-enabled Meteor data
const AppWithUserId = createContainer(() => {
return {
userId: Meteor.userId(),
};
}, AppWithData);
export default AppWithUserId;
I would really appreciate some pointers.
I believe the error is that you accidentally ended the render function before the return statement.
render() { // <- here it starts
let userId = this.props.userId;
let currentUser = this.props.currentUser;
} // <- here it ends
Another error is that your return statement doesn't return a single DOM element, but two of them: an Accounts.ui.LoginForm and a div. The return function should only return one element. Just put the entire thing into a single <div>.

Resources