Post 6: Enhancing the Frontend with Styling and Validations
In Post 5, we created a functional frontend UI with React to interact with our backend API. While it works, it lacks polish and proper form validations. In this post, we’ll enhance the user experience by integrating styling with Tailwind CSS and implementing robust client-side form validations.
1. Adding Styling with Tailwind CSS
Install Tailwind CSS
In the frontend
directory, install Tailwind CSS and initialize it:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init
Configure Tailwind
Update the tailwind.config.js
file to include your React files:
module.exports = {
content: ["./src/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
Import Tailwind in CSS
In src/index.css
, import Tailwind’s base styles:
@tailwind base;
@tailwind components;
@tailwind utilities;
Style Components
Let’s apply some basic styles to our components.
Updated UserList.js
:
const UserList = ({ onEdit, onDelete }) => {
const [users, setUsers] = useState([]);
useEffect(() => {
const fetchUsers = async () => {
try {
const response = await axios.get("/api/users");
setUsers(response.data);
} catch (error) {
console.error("Error fetching users:", error);
}
};
fetchUsers();
}, []);
return (
<div className="p-4 bg-gray-100 rounded-md shadow-md">
<h2 className="text-xl font-bold mb-4">User List</h2>
<ul>
{users.map(user => (
<li key={user._id} className="flex justify-between items-center bg-white p-3 mb-2 rounded shadow">
<div>
<p className="font-semibold">{user.name}</p>
<p className="text-sm text-gray-600">{user.email}</p>
</div>
<div>
<button className="text-blue-500 hover:text-blue-700 mr-2" onClick={() => onEdit(user)}>
Edit
</button>
<button className="text-red-500 hover:text-red-700" onClick={() => onDelete(user._id)}>
Delete
</button>
</div>
</li>
))}
</ul>
</div>
);
};
Updated UserForm.js
:
const UserForm = ({ selectedUser, onSave }) => {
const [formData, setFormData] = useState({ name: "", email: "", password: "" });
useEffect(() => {
if (selectedUser) {
setFormData(selectedUser);
}
}, [selectedUser]);
const handleChange = e => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleSubmit = async e => {
e.preventDefault();
try {
if (selectedUser) {
await axios.put(`/api/users/${selectedUser._id}`, formData);
} else {
await axios.post("/api/users", formData);
}
onSave();
setFormData({ name: "", email: "", password: "" });
} catch (error) {
console.error("Error saving user:", error);
}
};
return (
<form onSubmit={handleSubmit} className="p-4 bg-gray-100 rounded-md shadow-md">
<h2 className="text-xl font-bold mb-4">{selectedUser ? "Edit User" : "Add User"}</h2>
<div className="mb-4">
<input
name="name"
value={formData.name}
onChange={handleChange}
placeholder="Name"
className="w-full p-2 border rounded"
required
/>
</div>
<div className="mb-4">
<input
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
className="w-full p-2 border rounded"
required
/>
</div>
<div className="mb-4">
<input
name="password"
value={formData.password}
onChange={handleChange}
placeholder="Password"
type="password"
className="w-full p-2 border rounded"
required
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-700"
>
{selectedUser ? "Update User" : "Add User"}
</button>
</form>
);
};
2. Adding Client-Side Validations
To ensure data integrity, let’s validate form inputs before submission.
Validation Rules
- Name: At least 3 characters.
- Email: Must be a valid email format.
- Password: At least 6 characters.
Updated UserForm.js
with Validation:
const UserForm = ({ selectedUser, onSave }) => {
const [formData, setFormData] = useState({ name: "", email: "", password: "" });
const [errors, setErrors] = useState({});
const validate = () => {
const errors = {};
if (!formData.name || formData.name.length < 3) {
errors.name = "Name must be at least 3 characters long.";
}
if (!formData.email || !/\S+@\S+\.\S+/.test(formData.email)) {
errors.email = "Email must be a valid email address.";
}
if (!formData.password || formData.password.length < 6) {
errors.password = "Password must be at least 6 characters long.";
}
return errors;
};
const handleSubmit = async e => {
e.preventDefault();
const validationErrors = validate();
if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors);
return;
}
try {
if (selectedUser) {
await axios.put(`/api/users/${selectedUser._id}`, formData);
} else {
await axios.post("/api/users", formData);
}
onSave();
setFormData({ name: "", email: "", password: "" });
setErrors({});
} catch (error) {
console.error("Error saving user:", error);
}
};
return (
<form onSubmit={handleSubmit} className="p-4 bg-gray-100 rounded-md shadow-md">
<h2 className="text-xl font-bold mb-4">{selectedUser ? "Edit User" : "Add User"}</h2>
<div className="mb-4">
<input
name="name"
value={formData.name}
onChange={e => setFormData({ ...formData, name: e.target.value })}
placeholder="Name"
className="w-full p-2 border rounded"
required
/>
{errors.name && <p className="text-red-500 text-sm">{errors.name}</p>}
</div>
<div className="mb-4">
<input
name="email"
value={formData.email}
onChange={e => setFormData({ ...formData, email: e.target.value })}
placeholder="Email"
className="w-full p-2 border rounded"
required
/>
{errors.email && <p className="text-red-500 text-sm">{errors.email}</p>}
</div>
<div className="mb-4">
<input
name="password"
value={formData.password}
onChange={e => setFormData({ ...formData, password: e.target.value })}
placeholder="Password"
type="password"
className="w-full p-2 border rounded"
required
/>
{errors.password && <p className="text-red-500 text-sm">{errors.password}</p>}
</div>
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-700"
>
{selectedUser ? "Update User" : "Add User"}
</button>
</form>
);
};
Next Steps
In Post 7, we’ll integrate Redux for state management to efficiently handle data across components. Stay tuned!
Top comments (0)