Can't find variable error when using SetState following user auth - firebase

I get the error
WARN Possible Unhandled Promise Rejection (id: 20):
ReferenceError: Can't find variable: setUser
I am using Firebase for User Authentication in a React Native app. When the user becomes logged in, I call setUser().
export default function App() {
// Set an initializing state whilst Firebase connects
const [initializing, setInitializing] = useState(true);
const [user, setUser] = useState();
useEffect(() => {
const subscriber = auth.onAuthStateChanged((_user) => {
if (!_user) {
...
}
else {
setUser(_user);
if (initializing) setInitializing(false);
}
});
return subscriber; // unsubscribe on unmount
},[]);
}
I use user to determine which Stack.Screen loads.
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Group>
{ user && <Stack.Screen name="CoreTabs" component={CoreTabs} options={{ headerShown: false, }} /> }
<Stack.Screen name="Login" component={LoginScreen} options={{ headerShown: false, }}/>
...
</Stack.Group>
<Stack.Group screenOptions={{ presentation: 'modal' }}>
...
</Stack.Group>
</Stack.Navigator>
</NavigationContainer>
);
The error surfaces when I change between screens. I am guessing the auth changes on other screens could be triggering the useEffect, but I don't understand why setUser is being read as a variable.

Related

NextJS - useSWR with token from session

I'm working with NextJS, Next-auth and Django as backend. I'm using the credentials provider to authenticate users. Users are authenticated against the Django backend and the user info together with the accesstoken is stored in the session.
I'm trying to use useSWR now to fetch data from the backend. (no preloading for this page required, that's why I'm working with SWR) I need to send the access_token from the session in the fetcher method from useSWR. However I don't know how to use useSWR after the session is authenticated. Maybe I need another approach here.
I tried to wait for the session to be authenticated and then afterwards send the request with useSWR, but I get this error: **Error: Rendered more hooks than during the previous render.
**
Could anybody help with a better approach to handle this? What I basically need is to make sure an accesstoken, which I received from a custom backend is included in every request in the Authorization Header. I tried to find something in the documentation of NextJS, Next-Auth or SWR, but I only found ways to store a custom access_token in the session, but not how to include it in the Header of following backend requests.
This is the code of the component:
import { useSession } from "next-auth/react";
import useSWR from 'swr';
import axios from 'axios'
export default function Profile() {
const { data: session, status } = useSession();
// if session is authenticated then fetch data
if (status == "authenticated") {
// create config with access_token for fetcher method
const config = {
headers: { Authorization: `Bearer ${session.access_token}` }
};
const url = "http://mybackend.com/user/"
const fetcher = url => axios.get(url, config).then(res => res.data)
const { data, error } = useSWR(url, fetcher)
}
if (status == "loading") {
return (
<>
<span>Loading...</span>
</>
)
} else {
return (
<>
{data.email}
</>
)
}
}
you don't need to check status every time. what you need to do is to add this function to your app.js file
function Auth({ children }) {
const router = useRouter();
const { status } = useSession({
required: true,
onUnauthenticated() {
router.push("/sign-in");
},
});
if (status === "loading") {
return (
<div> Loading... </div>
);
}
return children;
}
then add auth proprety to every page that requires a session
Page.auth = {};
finally update your const App like this
<SessionProvider session={pageProps.session}>
<Layout>
{Component.auth ? (
<Auth>
<Component {...pageProps} />
</Auth>
) : (
<Component {...pageProps} />
)}
</Layout>
</SessionProvider>
so every page that has .auth will be wrapped with the auth component and this will do the work for it
now get rid of all those if statments checking if session is defined since you page will be rendered only if the session is here
Thanks to #Ahmed Sbai I was able to make it work. The component now looks like this:
import { useSession } from "next-auth/react";
import axios from "axios";
import useSWR from 'swr';
Profile.auth = {}
export default function Profile() {
const { data: session, status } = useSession();
// create config with access_token for fetcher method
const config = {
headers: { Authorization: `Bearer ${session.access_token}` }
};
const url = "http://mybackend.com/user/"
const fetcher = url => axios.get(url, config).then(res => res.data)
const { data, error } = useSWR(url, fetcher)
if (data) {
return (
<>
<span>{data.email}</span>
</>
)
} else {
return (
<>
Loading...
</>
)
}
}
App component and function:
function Auth({ children }) {
const router = useRouter();
const { status } = useSession({
required: true,
onUnauthenticated() {
router.push("/api/auth/signin");
},
});
if (status === "loading") {
return (
<div> Loading... </div>
);
}
return children;
}
function MyApp({
Component,
pageProps: { session, ...pageProps },
}) {
return (
<SessionProvider session={pageProps.session}>
{Component.auth ? (
<Auth>
<Component {...pageProps} />
</Auth>
) : (
<Component {...pageProps} />
)}
</SessionProvider>
)
}

Right way to keep Firebase user data and session stored - React Native

I have been looking on several ways to keep the user logged in my react native application. One of those are:
auth.onAuthStateChanged((user) => {
if (user) {
setIsAuthenticated(true);
} else {
setIsAuthenticated(false);
});
Which isn't working for me, whenever I press r on the application to hot refresh, it losses the user.
I'm currently storing the user email, uid on the local storage, and only loggin it out whenever he presses the logout button. Is this the right way to do so? I have encountered some problems, for example. I can't use auth().currentUser since the data is stored in async storage.
This is how i'm managing it:
export default function App() {
// Autentication
const [isAuthenticated, setIsAuthenticated] = useState(false);
// Dark mode
const [darkMode, setDarkMode] = useState(false);
// Setting location
let [locale, setLocale] = useState(Localization.locale);
i18n.fallbacks = true;
i18n.translations = { en, pt };
i18n.locale = locale;
// Settings fonts
const [fontsLoaded] = useFonts({
Roboto_400Regular,
Roboto_500Medium,
Roboto_700Bold,
});
useEffect(() => {
setLocale('pt');
getData('#rememberUser')
.then((data) => data && setIsAuthenticated(true))
.catch(() => console.log('No user on async'));
// auth.onAuthStateChanged((user) => {
// if (user) {
// setIsAuthenticated(true);
// } else {
// setIsAuthenticated(false);
// }
// console.log(user);
// });
}, []);
if (!fontsLoaded) return <></>;
return (
<DataProvider
setIsAuthenticated={setIsAuthenticated}
setDarkMode={setDarkMode}
darkMode={darkMode}
>
<ThemeProvider theme={darkMode ? darkTheme : lightTheme}>
{!isAuthenticated ? (
<NavigationContainer>
<AuthRoutes />
</NavigationContainer>
) : (
<Provider store={store}>
<Main />
</Provider>
)}
<ToastMessage />
</ThemeProvider>
</DataProvider>
);
}
storing:
async function storeData(value: any) {
try {
await clearAll();
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem('#rememberUser', jsonValue);
} catch (error) {
console.log('Error on async storage');
}
}
Is this the right way to keep track of the user?

I keep on getting "java.lang.IncompatibleClassChangeError" when I try to use RNFirebase auth method on my react native app

I keep getting the following error and my app crashes when I try to use the method createUserWithEmailAndPassword or signInWithPhoneNumber from RNFirebase on my react native app. This is currently happening only on the android version of my app.
I did all that was mentioned in the documentation, including enabling Android Device Verification API on my Google Cloud Platform while I was trying to have the signInWithPhoneNumber method work.
I kept on getting the above-mentioned error on Android 7.0 on a Samsung S6 and Huawei Nova 2i.
Below is the code I used:
import { View, Text } from 'react-native'
import React, { useState } from 'react'
import styles from './styles'
import { Button, TextInput } from 'react-native-paper'
import auth from '#react-native-firebase/auth';
function LoginScreen() {
const [emailID, setEmailID] = useState("");
const [password, setPassword] = useState("");
const [confirm, setConfirm] = useState(null);
const [code, setCode] = useState('');
async function loginWithEmail (email, password) {
await auth()
.createUserWithEmailAndPassword('jane.doe#example.com', 'SuperSecretPassword!')
.then(() => {
console.log('User account created & signed in!');
})
.catch(error => {
if (error.code === 'auth/email-already-in-use') {
console.log('That email address is already in use!');
}
if (error.code === 'auth/invalid-email') {
console.log('That email address is invalid!');
}
console.error(error);
});
}
return (
<View style={styles.container}>
<View style={styles.textInputContainer}>
<TextInput mode="outlined"
label='Enter your email'
keyboardType="email-address"
value={emailID}
onChangeText={(e) => setEmailID(e)} />
<TextInput mode="outlined"
label='Enter your password'
secureTextEntry
value={password}
onChangeText={(e) => setPassword(e)} />
</View>
<View style={styles.buttonContainers}>
<Button mode="contained" style={styles.signUpButton} disabled={emailID.length > 5 && password.length > 6 ? false : true} onPress={() => loginWithEmail(emailID, password)}>Login</Button>
</View>
</View>
)
}
export default LoginScreen
Any help in pointing out where I went wrong would be of great help. Thank you

Fetching data from Firebase and storing in local state is undefined

I am fetching data from firebase with the result being undefined. When I comment out the elements that use the data, the data gets stored in state, I uncomment the elements and they render without any issues. When I make changes and save the file, it reverts back to being undefined and the elements do not render.
Here is the code I am currently working with :
const router = useRouter();
const { make, model, id } = router.query;
const [singleCar, setSingleCar] = useState([]);
const docRef = doc(db, "car listings", `${id}`);
useEffect(() => {
const fetchDetails = async () => {
await getDoc(docRef).then((doc) => {
const carData = doc.data();
setSingleCar(carData);
});
await onSnapshot(docRef, (doc) => {
const carData = doc.data();
setSingleCar(carData);
});
};
fetchDetails();
}, [singleCar]);
I know I am both fetching a single doc and adding in a real-time listener but I wasn't sure which would be the best action, given the getDoc method only runs once after fetching the document so thought it might be better to have a real-time listener also in place.
Here is what I am rendering, my thinking is that if state is undefined, render the Loader component until the state has changed with the appropriate data.
{singleCar === [] ? (
<Loader />
) : (
<Container maxW="container.xl">
<Box display="flex" justifyContent="space-around">
<Image
w="500px"
src={singleCar.carImages[0].fileURL}
borderRadius="10px"
/>
{/* <CarImageGalleryModal isOpen={isOpen} onClose={onClose} /> */}
<Box
display="flex"
flexDirection="column"
justifyContent="space-between"
>
<SingleCarPrimary
make={singleCar.carDetails.make}
model={singleCar.carDetails.model}
year={singleCar.carDetails.year}
doors={singleCar.carDetails.doors}
engineSize={singleCar.carDetails.engine_size}
fuelType={singleCar.carDetails.fuel_type}
body={singleCar.carDetails.year}
price={singleCar.carPrice}
/>
<ContactSection />
</Box>
</Box>
<CarSummary carDetails={singleCar.carDetails} />
<SingleCarDescription carDescription={singleCar.carDescription} />
</Container>
)}
Here is how the information is being stored in Firebase to give you an idea of what data is being retrieved.
Backend: Firebase version 9
Frontend: Next.js / Chakra UI
The issue should be with the queried document identifier that is not referring to any document, hence the undefined properties result you are getting.
Here down a list of fixes you should apply to your implementation to mitigate such an issue.
You are better using a separate loading state that is will be reset once data is loaded so that it can reflect both loading and empty results:
const router = useRouter();
const { make, model, id } = router.query;
const [singleCar, setSingleCar] = useState({}); // the car should be an object and not an array
const [loading, setLoading] = useState(true);
const docRef = doc(db, "cars", `${id}`); // avoid spaces in document names
useEffect(() => {
const fetchDetails = async () => {
await getDoc(docRef).then((doc) => {
setLoading(false);
if (doc.exists()) {
setSingleCar(doc.data());
}
});
};
// fetch the `car` data
fetchDetails();
// then attach the change listener
await onSnapshot(docRef, (doc) => {
const carData = doc.data();
setSingleCar(carData);
});
}, []); // you should not rerun the effect on state changes as this will keep on reattaching state listener again and again and may fall into endless fetching loops
You component declaration then may be updated as follows (you may still need to update it to reflect empty car state => the data is fetched but there is not car reflecting the queried id):
{loading === true ? (
<Loader />
) : (
<Container maxW="container.xl">
<Box display="flex" justifyContent="space-around">
<Image
w="500px"
src={singleCar.carImages[0].fileURL}
borderRadius="10px"
/>
{/* <CarImageGalleryModal isOpen={isOpen} onClose={onClose} /> */}
<Box
display="flex"
flexDirection="column"
justifyContent="space-between"
>
<SingleCarPrimary
make={singleCar.carDetails.make}
model={singleCar.carDetails.model}
year={singleCar.carDetails.year}
doors={singleCar.carDetails.doors}
engineSize={singleCar.carDetails.engine_size}
fuelType={singleCar.carDetails.fuel_type}
body={singleCar.carDetails.year}
price={singleCar.carPrice}
/>
<ContactSection />
</Box>
</Box>
<CarSummary carDetails={singleCar.carDetails} />
<SingleCarDescription carDescription={singleCar.carDescription} />
</Container>
)}
I was able to fix this using the method of getServerSideProps which I think removes the need for useEffect.

Firebase Authentication - How To Deal With Delay

I have set-up my Firebase project as per the video from David East, as below with this in my app.js file. I have removed my config parameters.
#topName refers to an element on the page that displays the authenticated user's username. Unfortunately what happens is that someone logs in, or is logged in and goes to the page, it initially displays guest and then after some time it switches to the username of that user. This is quick (<500ms) but causes the page to render twice which is confusing.
How can I avoid this, do I need to store something in local storage?
(function() {
//Initialise Firebase
var config = {
apiKey: "",
authDomain: "",
databaseURL: "",
projectId: "",
storageBucket: "",
messagingSenderId: ""
};
firebase.initializeApp(config);
//Add a realtime listener.
firebase.auth().onAuthStateChanged(firebaseUser => {
if (firebaseUser) {
console.log(firebaseUser);
$('#topName').text(firebaseUser.email);
}
else
{
console.log('not logged in');
$('#topName').text("Guest");
}
});
}());
This is normal, it happens since the data that is being entered is being sent to the Firebase server, then you wait for a response from Firebase to check if this email is authenticated or not. Also internet connection can effect this.
So lots of stuff are happening in the background, to solve this maybe add a loading spinner widget, or try and store the credentials locally.
To solve this you can use localStorage example:
localStorage.setItem("lastname", "userx"); //store data
document.getElementById("result").innerHTML = localStorage.getItem("lastname") //to retrieve
For more info check this: https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
Or you can use sessionStorage, more info here: https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage
i solved this with the next code, just show a loader component while waiting for auth.onAuthStateChanged, the var has three values null, true and false.
const Routes = () => {
const dispatch = useDispatch();
// firebase observer for user auth
useEffect(() => {
let unsubscribeFromAuth = null;
unsubscribeFromAuth = auth.onAuthStateChanged(user => {
if (user) {
dispatch(setCurrentUser(user));
} else {
dispatch(clearCurrentUser());
}
});
return () => unsubscribeFromAuth();
}, [dispatch]);
return (
<Switch>
<Route exact path="/" component={Dashboard} />
<Route path="/signin">
<SignIn />
</Route>
<ProtectedRoute path="/protected">
<Dashboard />
</ProtectedRoute>
<Route path="*">
<NoMatch />
</Route>
</Switch>
);
};
const ProtectedRoute = ({ children, ...rest }) => {
const currentUser = useSelector(state => state.auth.currentUser);
if (currentUser === null) {
// handle the delay in firebase auth info if current user is null show a loader if is false user sign out
// TODO create a loading nice component
return <h1>Loading</h1>;
}
return (
<Route
// eslint-disable-next-line react/jsx-props-no-spreading
{...rest}
render={({ location }) =>
currentUser ? (
children
) : (
<Redirect
to={{
pathname: '/signin',
state: { from: location }
}}
/>
)
}
/>
);
};
export default Routes;

Resources