DEV Community

bilal khan
bilal khan

Posted on

The MERN stack series ! (P:6)

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
Enter fullscreen mode Exit fullscreen mode

Configure Tailwind

Update the tailwind.config.js file to include your React files:

module.exports = {
  content: ["./src/**/*.{js,jsx,ts,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
};
Enter fullscreen mode Exit fullscreen mode

Import Tailwind in CSS

In src/index.css, import Tailwind’s base styles:

@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

2. Adding Client-Side Validations

To ensure data integrity, let’s validate form inputs before submission.

Validation Rules

  1. Name: At least 3 characters.
  2. Email: Must be a valid email format.
  3. 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>
  );
};
Enter fullscreen mode Exit fullscreen mode

Next Steps

In Post 7, we’ll integrate Redux for state management to efficiently handle data across components. Stay tuned!

Top comments (0)