As a React developer with over eight years of experience, I’ve built countless components, but the login form remains one of the most essential and frequently used. Whether you’re creating a corporate app for any company or a consumer-facing platform, a clean, functional login component is crucial.
In this article, I’ll walk you through building a React login component from scratch. I’ll share practical tips and best practices based on real-world projects I’ve worked on, so you can create a component that’s both user-friendly and easy to maintain.
Build a Custom Login Component in React
Many developers rely on third-party libraries or external authentication services. While these are great, sometimes you need a lightweight, customizable login form tailored to your app’s unique requirements.
From my experience working with US clients, I’ve learned that a custom login component helps:
- Maintain consistent branding and UI style
- Control validation logic precisely
- Integrate easily with backend APIs or services like Firebase or AWS Cognito
Method 1: Simple React Login Component with State and Validation
Let’s start with the basics, a simple login form that accepts email and password, validates input, and handles submission.
Step 1: Set Up the Component
Create a new file Login.js, and use React’s useState hook to manage form data and errors.
import React, { useState } from 'react';
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [errors, setErrors] = useState({});
const validate = () => {
const errors = {};
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!email) {
errors.email = 'Email is required';
} else if (!emailRegex.test(email)) {
errors.email = 'Email is invalid';
}
if (!password) {
errors.password = 'Password is required';
} else if (password.length < 6) {
errors.password = 'Password must be at least 6 characters';
}
return errors;
};
const handleSubmit = (e) => {
e.preventDefault();
const validationErrors = validate();
setErrors(validationErrors);
if (Object.keys(validationErrors).length === 0) {
// Normally, here you would call your API to login
alert(`Logging in with Email: ${email}`);
// Reset form (optional)
setEmail('');
setPassword('');
}
};
return (
<div style={{ maxWidth: '400px', margin: 'auto', padding: '2rem' }}>
<h2>Login to Your Account</h2>
<form onSubmit={handleSubmit} noValidate>
<div style={{ marginBottom: '1rem' }}>
<label htmlFor="email">Email:</label><br />
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
style={{ width: '100%', padding: '0.5rem' }}
/>
{errors.email && <p style={{ color: 'red' }}>{errors.email}</p>}
</div>
<div style={{ marginBottom: '1rem' }}>
<label htmlFor="password">Password:</label><br />
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
style={{ width: '100%', padding: '0.5rem' }}
/>
{errors.password && <p style={{ color: 'red' }}>{errors.password}</p>}
</div>
<button type="submit" style={{ padding: '0.5rem 1rem' }}>
Login
</button>
</form>
</div>
);
};
export default Login;You can refer to the screenshot below to see the output.

- We use controlled inputs to keep track of the email and password.
- The validate function checks for empty fields and validates the email format.
- On submit, if there are no errors, we simulate a login action with an alert.
- Error messages display below each input if validation fails.
Method 2: Add Formik and Yup for Advanced Validation
For more complex forms, I often use Formik with Yup to handle form state and validation elegantly. This approach reduces boilerplate and improves scalability.
Step 1: Install Dependencies
npm install formik yupStep 2: Create the Login Component with Formik
Now let’s create the Login component using Formik and Yup to handle form state and validation efficiently.
import React from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
const Login = () => {
const formik = useFormik({
initialValues: {
email: '',
password: '',
},
validationSchema: Yup.object({
email: Yup.string()
.email('Invalid email address')
.required('Required'),
password: Yup.string()
.min(6, 'Password must be at least 6 characters')
.required('Required'),
}),
onSubmit: (values) => {
alert(`Logging in with Email: ${values.email}`);
},
});
return (
<div style={{ maxWidth: '400px', margin: 'auto', padding: '2rem' }}>
<h2>Login to Your Account</h2>
<form onSubmit={formik.handleSubmit} noValidate>
<div style={{ marginBottom: '1rem' }}>
<label htmlFor="email">Email:</label><br />
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
style={{ width: '100%', padding: '0.5rem' }}
/>
{formik.touched.email && formik.errors.email ? (
<p style={{ color: 'red' }}>{formik.errors.email}</p>
) : null}
</div>
<div style={{ marginBottom: '1rem' }}>
<label htmlFor="password">Password:</label><br />
<input
id="password"
name="password"
type="password"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.password}
style={{ width: '100%', padding: '0.5rem' }}
/>
{formik.touched.password && formik.errors.password ? (
<p style={{ color: 'red' }}>{formik.errors.password}</p>
) : null}
</div>
<button type="submit" style={{ padding: '0.5rem 1rem' }}>
Login
</button>
</form>
</div>
);
};
export default Login;You can refer to the screenshot below to see the output.

- Formik manages form state and submission elegantly.
- Yup provides declarative schema validation.
- This combo reduces manual error handling and improves code readability.
- It’s especially useful for larger forms or when you want to extend validation rules.
Method 3: Handle Authentication with Context API
In real applications, login is more than just a form. You need to manage authentication state globally.
Here’s a quick example of how you might integrate the login component with React’s Context API to manage user authentication.
Step 1: Create an AuthContext
Create an authentication context to store and manage the user’s login state globally.
import React, { createContext, useState, useContext } from 'react';
const AuthContext = createContext();
export const useAuth = () => useContext(AuthContext);
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const login = (email, password) => {
// Replace this with real API call
if (email === 'user@example.com' && password === 'password123') {
setUser({ email });
return true;
}
return false;
};
const logout = () => setUser(null);
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
};Step 2: Update Login Component to Use AuthContext
Update the Login component to use the AuthContext for handling real login logic.
import React, { useState } from 'react';
import { useAuth } from './AuthContext';
const Login = () => {
const { login } = useAuth();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
const success = login(email, password);
if (!success) {
setError('Invalid email or password');
} else {
setError('');
alert('Login successful!');
}
};
return (
<div style={{ maxWidth: '400px', margin: 'auto', padding: '2rem' }}>
<h2>Login to Your Account</h2>
<form onSubmit={handleSubmit} noValidate>
<div style={{ marginBottom: '1rem' }}>
<label htmlFor="email">Email:</label><br />
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
style={{ width: '100%', padding: '0.5rem' }}
/>
</div>
<div style={{ marginBottom: '1rem' }}>
<label htmlFor="password">Password:</label><br />
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
style={{ width: '100%', padding: '0.5rem' }}
/>
</div>
{error && <p style={{ color: 'red' }}>{error}</p>}
<button type="submit" style={{ padding: '0.5rem 1rem' }}>
Login
</button>
</form>
</div>
);
};
export default Login;Step 3: Wrap Your App with AuthProvider
Wrap your entire app with AuthProvider so authentication works across all components.
import React from 'react';
import ReactDOM from 'react-dom';
import { AuthProvider } from './AuthContext';
import Login from './Login';
const App = () => (
<AuthProvider>
<Login />
</AuthProvider>
);
ReactDOM.render(<App />, document.getElementById('root'));You can see the output in the screenshot below.

Building a React login component is a foundational skill for any React developer, especially when working on projects for US clients where user experience and security are paramount.
The first method I shared is perfect for beginners or quick prototypes. If you want more robust validation, Formik and Yup are your friends. And for real-world apps, integrating authentication state with React Context or other state management libraries is essential.
Remember, the login component is often the user’s first impression of your app. Keep it simple, clear, and responsive.
You may also like to read:
- How to Import CSS Files in React Components
- Optimize React Functional Components with React.memo
- How to Fix React Component Not Defined Error
- How to Pass Ref to Child Component in React

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.