I’m building an Expo (SDK 53) React Native app with Firebase Authentication and I keep getting this error on startup:
[runtime not ready]: Error: Component auth has not been registered yet, js engine: hermes
I’ve tried many variations—renaming files, moving imports, clearing caches, but nothing seems to work. I suspect the native entry point isn’t wired up correctly. Below is a minimal reproduction of my setup.
package.json
{
"main": "index.js",
"scripts": {
"start": "expo start --dev-client",
…
},
"dependencies": {
"expo": "~53.0.5",
"firebase": "^11.6.1",
"@react-native-async-storage/async-storage": "^1.24.0",
"react-native-gesture-handler": "^2.25.0",
"react-native-safe-area-context": "^5.4.0",
"@react-navigation/native": "^7.1.6",
"@react-navigation/native-stack": "^7.3.10",
…
}
}
firebase.js
import AsyncStorage from "@react-native-async-storage/async-storage";
import { initializeApp } from "firebase/app";
import { getReactNativePersistence, initializeAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
const firebaseConfig = {
/* my config… */
};
const app = initializeApp(firebaseConfig);
export const auth = initializeAuth(app, {
persistence: getReactNativePersistence(AsyncStorage),
});
export const db = getFirestore(app);
AuthContext.js
import { onAuthStateChanged } from "firebase/auth";
import React, { createContext, useEffect, useState } from "react";
import { auth } from "../services/firebase";
export const AuthContext = createContext({});
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const unsub = onAuthStateChanged(auth, u => {
setUser(u);
setLoading(false);
});
return unsub;
}, []);
return (
<AuthContext.Provider value={{ user, loading, setUser }}>
{children}
</AuthContext.Provider>
);
}
What I’ve tried
Nothing fixes it. The native console still complains that it can’t find a registered component named “auth.” I’m not explicitly calling AppRegistry.registerComponent('auth', …), so it looks like Expo/Metro is picking up something strange.
Instead of using expo-dev client, I continued to use expo go. I simply added the following line in my metro.config.js file:
const defaultConfig = getDefaultConfig(__dirname);
defaultConfig.resolver.sourceExts.push('cjs');
// This is the new line you should add in, after the previous lines
defaultConfig.resolver.unstable_enablePackageExports = false;
After that, I didn't seem to get the error "Component auth has not been registered yet".
1. Clean up node_modules and lockfile:
rm -rf node_modules package-lock.json
npm install
2. Setup firebase.ts:
import ReactNativeAsyncStorage from '@react-native-async-storage/async-storage';
import { initializeApp } from "firebase/app";
//@ts-ignore
import { getReactNativePersistence, initializeAuth } from 'firebase/auth';
import { getFirestore } from "firebase/firestore";
import { getStorage } from 'firebase/storage';
const firebaseConfig = {
// your config here
};
const app = initializeApp(firebaseConfig);
export const auth = initializeAuth(app, {
persistence: getReactNativePersistence(ReactNativeAsyncStorage),
});
export const db = getFirestore(app);
export const storage = getStorage(app);
metro.config.js:
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.resolver.sourceExts.push('cjs');
config.resolver.unstable_enablePackageExports = false;
module.exports = config;
tsconfig.json:
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"paths": {
"@firebase/auth": ["./node_modules/@firebase/auth/dist/index.rn.d.ts"],
"@/*": ["./*"]
}
},
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
}
Packages:
"@react-native-async-storage/async-storage": "^2.1.2",
"firebase": "^11.8.1"
import { auth } from '@/lib/firebase';
import { createUserWithEmailAndPassword, signInWithEmailAndPassword } from 'firebase/auth';
import React, { useState } from 'react';
import { SafeAreaView, Text, TextInput, TouchableOpacity } from 'react-native';
export default function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const signIn = async () => {
try {
const user = await signInWithEmailAndPassword(auth, email, password);
alert('Sign in successful: ' + user.user.email);
} catch (error: any) {
alert('Sign in failed: ' + error.message);
}
};
const signUp = async () => {
try {
const user = await createUserWithEmailAndPassword(auth, email, password);
alert('Sign up successful: ' + user.user.email);
} catch (error: any) {
alert('Sign up failed: ' + error.message);
}
};
return (
<SafeAreaView>
<Text>Login</Text>
<TextInput placeholder="email" value={email} onChangeText={setEmail} />
<TextInput placeholder="password" value={password} onChangeText={setPassword} secureTextEntry />
<TouchableOpacity onPress={signIn}>
<Text>Login</Text>
</TouchableOpacity>
<TouchableOpacity onPress={signUp}>
<Text>Make Account</Text>
</TouchableOpacity>
</SafeAreaView>
);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With