
Setting Up Persistent Authentication With Firebase 9, React and Redux
While working on one of my projects, I had a bit of trouble trying to add persistent authentication with Firebase and so this is my solution. I don’t want this guide to be too long, so I will not be covering how to implement Firebase sign-in and sign-out.
Hope it helps someone! :)
Dependencies
Make sure to already have a React project and a Firebase project created. As
well as to install npm packages for both firebase
and react-redux
.
You can install them with:
npm install @reduxjs/toolkit react-redux firebase
Setting up Firebase
Before everything, make sure to set up your firebase config. I am using typescript,
so this file will be called firebase.ts
. In my project, I will be placing this
file in a utils
folder.
// file: utils/firebase.ts
const firebaseConfig = {
/*
COPY PASTE YOUR CONFIG HERE
*/
apiKey: "xxxxxxxxxxxxxxxxxxxx",
authDomain: "xxxxxxxxxxxxxxxxxxxx",
projectId: "xxxxxxxxxxxxxxxxxxxx",
storageBucket: "xxxxxxxxxxxxxxxxxxxx",
messagingSenderId: "xxxxxxxxxxxxxxxxxxxx",
appId: "xxxxxxxxxxxxxxxxxxxx",
};
const firebaseApp = initializeApp(firebaseConfig);
const auth = getAuth(firebaseApp);
export { auth, firebaseApp };
Add a provider in firebase
If you haven’t already, we will need to add a provider in Firebase in order to enable sign-in using email and password.
In the Firebase console, go to your
Authentication
dashboard and then click on the Sign-in method
tab. From here,
we should be able to see a Add new provider
button.
Click on this button and then under Native Providers
, enable email/password
.
Setting up Redux
If you have already set up Redux in your project, feel free to skip this section!
According to the official Redux docs, we should create slices within a folder
called features
. I prefer to have all my Redux related files organized within
a single a folder. So we’ll be adding all of our Redux related files within a
folder called store
.
What our folder structure will look like
This will be how our Redux folder structure will look like.
src/store
├── features
│ └── user
│ └── userSlice.ts
├── index.ts
└── reducers.ts
Create our store
In our store
folder create a file called index.ts
. This is where we will
be setting up our Redux store. We name the file index.ts
so that when we export
our Redux store, we can easily import it to our app with
import store from './store';
Place this code in store/index.ts
:
// file: src/store/index.ts
import { configureStore } from "@reduxjs/toolkit";
const store = configureStore({
reducer: () => ({}),
});
export default store;
Then we will need to make sure we provide the store to our entire app.
Within our React project and in the file where you mount your root app
(this should be in src/index.ts
), import Provider
from react-redux
and
our Redux store. Then wrap your App component with the component <Provider>
.
// file: src/index.ts
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import App from "./App";
import store from "./store";
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
If you need more information on how all of this code works, you can check out Redux’s official docs on getting started with Redux.
Creating our user slice
The user slice will be responsible for all of our state logic and actions for users. You can find more information on slices in the official Redux docs. In our user slice, a user is logged out when the state is null and logged in when there is User data.
// file: ./store/features/user/userSlice
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { User } from "firebase/auth";
interface UserState {
user: User | null;
}
const initialState: UserState = {
user: null,
};
const userSlice = createSlice({
name: "user",
initialState: initialState,
reducers: {
logout() {
return {
...initialState,
};
},
login(state: UserState, action: PayloadAction<User>) {
return {
user: action.payload,
};
},
},
});
// selectors
export const selectUser = (state: UserState) => state.user;
// actions
export const { startLoading, stopLoading, login, logout } = userSlice.actions;
export default userSlice.reducer;
With the reducers file in place, we can now add our user reducer to our list of
reducers.
I like to create a separate file for this so that the store/index.ts
file is
a bit cleaner. So we will create a new file called reducers.ts
in the store
folder to hold all of our reducers. And to combine our reducers, we will use a
built-in Redux method called combineReducers
.
Here’s what this code will look like:
// file: ./store/reducers.ts
import { combineReducers } from "redux";
import userReducer from "./features/user/userSlice";
const rootReducer = combineReducers({
user: userReducer,
// OTHER REDUCERS HERE
});
export default rootReducer;
You also need to make sure the store knows about these
reducers. So we will also update ./store/index.ts
:
// file: ./store/index.ts
import { configureStore } from "@reduxjs/toolkit";
import rootReducer from "./reducers";
const store = configureStore({
// NEW CODE
reducer: rootReducer,
// NEW CODE
});
export default store;
Loading persistent user data on mount
Ok! We’ve finally gotten redux and firebase up and running. Now to make user login persistent.
In order to watch for changes in our Firebase authentication state, we will
need to use the method onAuthStateChange
. It is a observer
that watches for
changes in our Firebase authentication state. And since it’s an observer, we
will also need to make sure we clean it up by unsubscribing to the observer
when the component unmounts.
import { useEffect } from "react";
import { useDispatch } from "react-redux";
import { login, logout } from "./store/features/user/userSlice";
function App() {
const dispatch = useDispatch();
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (userAuth) => {
if (userAuth) {
dispatch(login(userAuth));
} else {
dispatch(logout());
}
});
return () => {
unsubscribe();
dispatch(logout());
};
}, []);
return (
// ...
// component logic here
// ...
);
}
Accessing user data within a React component
In order to access our user’s data and login status, we need to import
selectUser
which we’ve already implemented in our userSlice.ts
file.
Combined with useSelector
, we will be able to grab the data from within our
Redux store.
import { selectUser } from "./store/features/user/userSlice";
import { useSelector } from "react-redux";
import type { User } from "firebase/auth";
function myComponent() {
const user: User | null = useSelector(selectUser);
// ...
// component logic here
// ...
}
Annnnd we are done. Let me know if there are any mistakes :D