Motivation
Temporarily, I'm a full-time front-end developer for a project. It utilizes Redux, but when I started, I lacked both React and Redux experience. While I've been on this project for three months now, I've primarily relied on existing code to implement Redux functionalities without grasping the core concepts. Understanding the intricacies of the Redux store and its inner workings remained elusive. Thankfully, with guidance from senior colleagues and Stack Overflow, I've managed to navigate runtime issues.
However, I'm determined to delve deeper and solidify my understanding of Redux from the ground up. This way, I can troubleshoot problems with confidence and rely less on external resources for minor challenges.
To efficiently learn two technologies simultaneously, I decided to implement Firebase Authentication while storing user metadata and auth tokens using Redux.
Setup (Firebase)
Go to the Firebase console (https://console.firebase.google.com/) and create a new project.
Give your project a name and enable billing if necessary.
Register your app:
Select the platform you're developing for (web, Android, iOS, etc.) and follow the specific instructions for registering your app.
This will give you a configuration file or snippet that you need to add to your project.
** Install the Firebase SDK:**
Use the package manager for your platform (npm for JavaScript, CocoaPods for iOS, etc.) to install the required Firebase SDK libraries.
Initialize Firebase in your app:
Use the configuration file or snippet you obtained earlier to initialize Firebase in your app.
This will enable you to use Firebase services in your code.
Create Register Component
Notice, I the code I also have sent another call to sendEmailVerification
so user cannot register with a bogus email
const Register = () => {
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const register = async () => {
setLoading(true);
await createUserWithEmailAndPassword(auth, email, password)
.then(async (userCredential: any) => {
await sendEmailVerification(userCredential.user);
navigate(RouteEnums.VERIFICATION_NOTICE, { state : userCredential.user.email });
})
.catch((error: any) => {
toast.error(error.message);
}).finally(()=> {
setLoading(false)
});
};
return (
<>
{loading ? <Spinner /> : (
<div className="flex min-h-full flex-1 flex-col justify-center w-[400px] px-6 py-12 lg:px-8">
<div className="sm:mx-auto sm:w-full sm:max-w-sm">
<img
className="mx-auto h-10 w-auto"
src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=600"
alt="Your Company"
/>
<h2 className="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
Register a new account
</h2>
</div>
<div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
<div>
<label
htmlFor="email"
className="block text-sm font-medium leading-6 text-gray-900"
>
Email address
</label>
<div className="mt-2">
<input
onChange={(e) => setEmail(e.target.value)}
id="email"
name="email"
type="email"
autoComplete="email"
required
className="block w-full rounded-md border-0 p-1.5 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>
</div>
</div>
<div className="my-5">
<div className="flex items-center justify-between">
<label
htmlFor="password"
className="block text-sm font-medium leading-6 text-gray-900"
>
Password
</label>
<div className="text-sm">
<a className="font-semibold text-indigo-600 hover:text-indigo-500">
Forgot password?
</a>
</div>
</div>
<div className="mt-2">
<input
onChange={(e) => setPassword(e.target.value)}
id="password"
name="password"
type="password"
autoComplete="current-password"
required
className="block w-full rounded-md border-0 p-1.5 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>
</div>
</div>
<div>
<button
onClick={register}
type="submit"
className="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Register
</button>
</div>
</div>
</div>
)}
</>
);
};
Verify the Register Function
If your configuration was successful your user would appear in the Authentication > Users
section on the firebase console
Create Login component
Notice I have only let user into my application if the user's email is verified
const Login = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const [loading, setLoading] = useState(false);
useEffect(() => {
console.log(loading)
},[loading])
const checkEmailVerified = (userCredential: any) => {
if (!userCredential.user.emailVerified) {
console.error("User email is not verified.");
toast.error("Please verify your email address to continue.");
} else {
dispatch(saveUser(userCredential.user));
dispatch(saveToken(userCredential.user.accessToken));
navigate(RouteEnums.HOME);
}
};
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const login = () => {
setLoading(true);
signInWithEmailAndPassword(auth, email, password)
.then((userCredential: any) => {
checkEmailVerified(userCredential);
})
.catch((error: any) => {
toast.error(error.message);
}).finally(()=> {
setLoading(false)
});
};
return (
<>
{loading ? <Spinner /> : (
<div className="flex min-h-full flex-1 flex-col justify-center w-[400px] py-12 lg:px-8">
<div className="sm:mx-auto sm:w-full sm:max-w-sm">
<img
className="mx-auto h-10 w-auto"
src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=600"
alt="Your Company"
/>
<h2 className="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
Login
</h2>
</div>
<div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
<div>
<label
htmlFor="email"
className="block text-sm font-medium leading-6 text-gray-900"
>
Email address
</label>
<div className="mt-2">
<input
onChange={(e) => setEmail(e.target.value)}
id="email"
name="email"
type="email"
autoComplete="email"
required
className="block w-full rounded-md border-0 p-1.5 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>
</div>
</div>
<div className="my-5">
<div className="flex items-center justify-between">
<label
htmlFor="password"
className="block text-sm font-medium leading-6 text-gray-900"
>
Password
</label>
<div className="text-sm">
<a className="font-semibold text-indigo-600 hover:text-indigo-500">
Forgot password?
</a>
</div>
</div>
<div className="mt-2">
<input
onChange={(e) => setPassword(e.target.value)}
id="password"
name="password"
type="password"
autoComplete="current-password"
required
className="block w-full rounded-md border-0 p-1.5 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>
</div>
</div>
<div>
<button
onClick={() => login()}
type="submit"
className="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Login
</button>
</div>
</div>
</div>
)}
</>
);
};
π Congratulations π you have implemented Auth using Firebase in your React app
Now Lets save the response from firebase in the Redux Store
Install the required packages:
Bash
npm install @reduxjs/toolkit react-redux @types/react-redux @types/react-router-dom (if using routing)
Store Setup:
Create a store.ts file to define your Redux store:
`import { configureStore } from "@reduxjs/toolkit";
import authReducer from "../reducer/user/userAuthSlice"
export const store = configureStore({
reducer: {
auth: authReducer,
}
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch`
Configure Reducers
import { createSlice } from "@reduxjs/toolkit"
const initialState: any = {
user: null,
authToken : null
}
const userAuthSlice = createSlice({
name : "auth",
initialState,
reducers: {
saveUser(state :any, action:any){
state.user = action.payload
},
saveToken(state :any, action:any){
state.authToken = action.payload
}
}
},
)
export const { saveUser , saveToken } : any = userAuthSlice.actions
export default userAuthSlice.reducer
Store the user Data and token in stores on successful login
dispatch(saveUser(userCredential.user));
dispatch(saveToken(userCredential.user.accessToken));
To protect the application create a guard to only allow authenticated user to login
const Router = () => {
const GUARD_ROUTE = (props: any) => {
const { children } = props;
const authToken: any = useSelector((state: any) => state.auth.authToken);
if (authToken) {
return <React.Fragment>{children}</React.Fragment>;
}
if (!authToken) {
toast.dismiss()
toast.error("Please Login First");
}
return <Navigate to={RouteEnums.LANDING} />;
};
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Landing />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/verification-sent" element={<VerificationNotice />} />
<Route
path="/home"
element={
<GUARD_ROUTE>
<Home />
</GUARD_ROUTE>
}
/>
</Routes>
</BrowserRouter>
);
};
export default Router;
π Congratulations you have implemented Redux as well π
Deployment
I used Vercel as it is the easiest and quickest option.
Steps
Push your app to a Git repository:
If you don't have one, create a new repository on GitHub, GitLab, or Bitbucket.
Push your local React app files to the remote repository.
Import your repository into Vercel:
- Go to https://vercel.com/dashboard
- Click "New Project" and select "From Git repository".
- Choose your Git provider and authorize Vercel to access your repositories.
- Select your repository and click "Import".
- Vercel will automatically detect your React app and deploy it.
You'll receive a deployment URL upon completion.
Top comments (0)