DEV Community

Cover image for Frontend Code Review Enhancing Team Code Quality
Tianya School
Tianya School

Posted on

Frontend Code Review Enhancing Team Code Quality

Today, let’s talk about frontend code review—a topic that’s often discussed but absolutely critical in team development. Whether you’re a newbie just starting out or a seasoned developer, code review is a powerful tool for improving code quality, fostering team collaboration, and reducing bugs. In frontend development, where code volume is high, iterations are fast, and requirements change frequently, poor code review can turn project maintenance into a nightmare.

Why Conduct Code Reviews?

Let’s start with why code reviews are so important. As frontend developers, writing code quickly is a strength, but it’s easy to introduce issues. Requirements change constantly, components pile up, and CSS conflicts, JavaScript logic bugs, and performance problems can emerge. Code review is like a “health check” for your code, catching issues early and preventing headaches. Beyond finding bugs, code reviews offer these benefits:

  • Improved Code Quality: Reviews catch sloppy code, potential bugs, and even optimize performance. Everyone makes small mistakes—reviews help clean them up.
  • Knowledge Sharing: Reviews let the team learn from clever solutions or discover new approaches from newcomers.
  • Consistent Code Style: A unified style prevents a mishmash of code, making maintenance easier.
  • Reduced Technical Debt: Catching issues early avoids “ship now, fix later” patches, lowering long-term maintenance costs.
  • Enhanced Team Collaboration: Reviews provide a chance for team discussions, strengthening relationships.

Of course, code reviews can feel “annoying.” Some see them as time-wasting, while others fear being criticized. But reviews aren’t about nitpicking—they’re about making code better and teams stronger. Let’s explore how to do code reviews effectively.

Preparing for Code Reviews

Effective code reviews require a clear process and preparation; otherwise, they can become chaotic and less effective. Here’s what to do in the preparation phase:

  • Define Review Goals: Before starting, clarify the focus—functionality, code style, or performance optimization? Clear goals make reviews efficient.
  • Use Tools: Tools like GitHub Pull Requests, GitLab Merge Requests, Bitbucket, CodeClimate, or SonarQube can automate checks for issues like inconsistent indentation or unused variables, saving effort.
  • Establish Standards: Agree on team-wide coding standards, such as ESLint rules, Prettier configs, or CSS naming conventions (e.g., BEM). Without standards, reviews can devolve into debates over style.
  • Assign Roles: Reviews typically involve the submitter (code author) and reviewers. Ideally, have at least two reviewers for diverse perspectives and fewer missed issues.
  • Set Timelines: Don’t let reviews drag on. Aim to complete reviews within 24-48 hours of a Pull Request to avoid delaying development.

With preparation done, reviews can be targeted and effective. Next, let’s dive into the review process and techniques.

Code Review Process and Techniques

Code reviews aren’t about skimming code and throwing out random comments—they need structure. Here’s a simple process suitable for most frontend teams:

  • Run the Code: Pull the code and run it to confirm it works as expected. Don’t just read—running it reveals hidden issues.
  • Read Through the Code: Review the code from start to finish to understand its logic before nitpicking.
  • Check Key Aspects: Focus on logic correctness, code standards, performance, and security vulnerabilities.
  • Write Comments: Be specific about issues, explaining why they’re problematic and suggesting improvements, not just saying “this is bad.”
  • Discuss and Revise: Discuss suggestions with the submitter, agree on changes, and respect their ownership by avoiding direct edits.
  • Verify Changes: Re-review after revisions to ensure issues are resolved.

Tone matters during reviews. Instead of commanding, “Fix this,” try, “Could we consider this approach for better results?” This fosters constructive dialogue.

Now, let’s walk through a real frontend code example to see how to review and suggest improvements.

Example Code: A React Component

Suppose your team is building an e-commerce site with a product list component that displays product info, supports pagination, and includes search functionality. Here’s the submitted React component code. Let’s review it.

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import './productList.css';

function ProductList() {
  const [products, setProducts] = useState([]);
  const [page, setPage] = useState(1);
  const [search, setSearch] = useState('');

  useEffect(() => {
    axios.get('https://api.example.com/products?page=' + page + '&search=' + search)
      .then(res => {
        setProducts(res.data);
      });
  }, [page, search]);

  const handleSearch = (event) => {
    setSearch(event.target.value);
    setPage(1);
  }

  const handlePageChange = (newPage) => {
    setPage(newPage);
  }

  return (
    <div>
      <input type="text" value={search} onChange={handleSearch} placeholder="Search products..." />
      <div className="product-list">
        {products.map(product => (
          <div className="product">
            <img src={product.image} />
            <h2>{product.name}</h2>
            <p>{product.price}</p>
            <button>Add to cart</button>
          </div>
        ))}
      </div>
      <div>
        <button onClick={() => handlePageChange(page - 1)}>Previous</button>
        <span>Page {page}</span>
        <button onClick={() => handlePageChange(page + 1)}>Next</button>
      </div>
    </div>
  );
}

export default ProductList;
Enter fullscreen mode Exit fullscreen mode
.product-list {
  display: flex;
  flex-wrap: wrap;
}
.product {
  width: 200px;
  margin: 10px;
  border: 1px solid #ccc;
  padding: 10px;
}
.product img {
  width: 100%;
}
button {
  background: blue;
  color: white;
  padding: 5px;
}
Enter fullscreen mode Exit fullscreen mode

At first glance, the code seems functional: it supports searching, pagination, and product listing with some styling. But as reviewers, we need to dig deeper. Let’s analyze step by step.

Code Review: Step-by-Step Analysis

Functionality Check

First, run the code to ensure it works. Install dependencies with npm install, run the app, and test:

  • Does the search update the list?
  • Do “Previous” and “Next” buttons work for pagination?
  • Are product images, names, and prices displayed correctly?

Testing reveals that search and pagination work, but the “Add to cart” button does nothing when clicked. This is an issue.

Comment 1: The Add to cart button lacks an event handler, making it non-functional. Suggest adding a handler, like calling an addToCart function, or at least a placeholder alert indicating the feature is pending.

Code Structure and Readability

Next, evaluate the code’s structure and readability. The logic is clear, but improvements are possible:

  • Variable Naming: Names like search and page are too generic and may conflict with other components. Suggest more semantic names like searchQuery and currentPage.
  • Component Splitting: The component handles search, pagination, and listing, making it lengthy and harder to maintain. Suggest splitting into sub-components like SearchBar, ProductItem, and Pagination.
  • Constant Extraction: The API URL https://api.example.com/products is hard-coded, making changes cumbersome. Suggest moving it to a constants file, e.g., constants/api.js.

Comment 2: Move the API URL to a separate config file, e.g., constants/api.js, for easier maintenance and environment switching (e.g., dev vs. prod).

Comment 3: Rename search and page to searchQuery and currentPage for better semantic clarity.

Comment 4: The component’s logic is complex. Suggest splitting into SearchBar, ProductItem, and Pagination sub-components for improved readability and maintainability.

Error Handling and Robustness

Frontend code often overlooks error handling, especially for network requests. Examine the useEffect with axios.get:

useEffect(() => {
  axios.get('https://api.example.com/products?page=' + page + '&search=' + search)
    .then(res => {
      setProducts(res.data);
    });
}, [page, search]);
Enter fullscreen mode Exit fullscreen mode

There’s no error handling. If the API fails (e.g., network issues, server 500), users see nothing. Add error handling:

Comment 5: The axios.get lacks error handling. Suggest adding .catch or try-catch (if using async/await) and displaying a user-friendly error message. Use an error state for UI feedback.

Improved code:

const [error, setError] = useState(null);

useEffect(() => {
  setError(null);
  axios.get(`https://api.example.com/products?page=${page}&search=${search}`)
    .then(res => {
      setProducts(res.data);
    })
    .catch(() => {
      setError('Failed to fetch products. Please try again.');
    });
}, [page, search]);
Enter fullscreen mode Exit fullscreen mode

Add error display in JSX:

{error && <div className="error">{error}</div>}
Enter fullscreen mode Exit fullscreen mode

Also, the URL parameter concatenation (page + '&search=' + search) is unsafe, especially if the search input contains special characters. Use template strings or URLSearchParams:

Comment 6: Use template strings or URLSearchParams for URL parameter concatenation to avoid issues with special characters. Example:

axios.get(`https://api.example.com/products?page=${page}&search=${encodeURIComponent(search)}`)
Enter fullscreen mode Exit fullscreen mode

Performance Optimization

Performance is a key focus in frontend reviews. Check for performance issues:

  • Excessive Requests: The useEffect triggers a request on every search or page change. Rapid search input changes cause multiple requests, wasting resources. Suggest debouncing.

Comment 7: Add debouncing to the search function to reduce unnecessary API requests. Use lodash’s debounce or a custom function.

Improved code:

import { debounce } from 'lodash';

const handleSearch = debounce((value) => {
  setSearch(value);
  setPage(1);
}, 300);

const handleInputChange = (event) => {
  handleSearch(event.target.value);
};
Enter fullscreen mode Exit fullscreen mode
  • Pagination Issues: The pagination buttons lack boundary checks. Clicking “Previous” when page is 1 sets it to 0, potentially causing API errors. Add boundary checks.

Comment 8: Add boundary checks to pagination buttons to ensure page stays ≥ 1. Update handlePageChange:

const handlePageChange = (newPage) => {
  if (newPage >= 1) {
    setPage(newPage);
  }
};
Enter fullscreen mode Exit fullscreen mode
  • List Rendering Performance: The products.map lacks a key prop, which React requires for efficient list updates and to prevent bugs.

Comment 9: The map in the list rendering lacks a key prop. Add a unique key, e.g., product.id, to each div.product.

Improved JSX:

{products.map(product => (
  <div key={product.id} className="product">
    <img src={product.image} alt={product.name} />
    <h2>{product.name}</h2>
    <p>{product.price}</p>
    <button>Add to cart</button>
  </div>
))}
Enter fullscreen mode Exit fullscreen mode

Code Standards and Style

Consistent code style is crucial for team collaboration. Check the style:

  • CSS Issues: The productList.css classes like .product and .product-list are too generic, risking conflicts. Suggest CSS Modules or BEM naming.

Comment 10: Use CSS Modules or BEM naming for CSS classes to avoid conflicts. Example: productList__item and productList__container.

Improved CSS (using BEM):

.product-list {
  display: flex;
  flex-wrap: wrap;
}
.product-list__item {
  width: 200px;
  margin: 10px;
  border: 1px solid #ccc;
  padding: 10px;
}
.product-list__item img {
  width: 100%;
}
.product-list__button {
  background: blue;
  color: white;
  padding: 5px;
}
Enter fullscreen mode Exit fullscreen mode
  • ESLint Compliance: Some code violates ESLint rules, e.g., handleSearch isn’t declared with const, risking accidental reassignment. Suggest running ESLint.

Comment 11: Run ESLint to ensure compliance with team standards. For example, declare handleSearch and handlePageChange with const.

Accessibility and User Experience

Frontend reviews must consider accessibility (a11y) and user experience:

  • Missing Alt Attributes: The <img src={product.image} /> lacks an alt attribute, making it inaccessible to screen reader users.

Comment 12: Add an alt attribute to product images, e.g., alt={product.name}, to improve accessibility.

  • Button Semantics: The “Add to cart” and pagination buttons lack ARIA attributes, confusing screen reader users.

Comment 13: Add ARIA attributes to buttons, e.g., aria-label or aria-disabled, for better accessibility. Example:

<button
  onClick={() => handlePageChange(page - 1)}
  disabled={page === 1}
  aria-label="Previous page"
>
  Previous
</button>
Enter fullscreen mode Exit fullscreen mode
  • Search Input UX: The placeholder “Search products...” is vague. Users may not know what fields are searchable.

Comment 14: Make the search placeholder more specific, e.g., “Search by product name or category,” or add help text.

Security Checks

Frontend code must address security, especially for API requests and user input:

  • Unescaped Search Input: The search input is concatenated into the URL without escaping, risking XSS or API errors.

Comment 15: Escape search input with encodeURIComponent to prevent issues with special characters (noted in Comment 6).

  • API Error Exposure: Displaying raw API error messages could leak sensitive information.

Comment 16: Avoid showing raw API error messages. Use user-friendly messages to prevent exposing sensitive data.

Test Coverage

Check if the code has tests. This code lacks any, increasing risk.

Comment 17: Add unit tests using Jest and React Testing Library to cover search, pagination, and rendering logic.

Example test code:

import { render, screen, fireEvent } from '@testing-library/react';
import ProductList from './ProductList';
import axios from 'axios';

jest.mock('axios');

test('renders product list and handles search', async () => {
  axios.get.mockResolvedValue({
    data: [
      { id: 1, name: 'Product 1', price: 10, image: 'img1.jpg' },
      { id: 2, name: 'Product 2', price: 20, image: 'img2.jpg' },
    ],
  });

  render(<ProductList />);

  expect(await screen.findByText('Product 1')).toBeInTheDocument();
  expect(screen.getByText('Product 2')).toBeInTheDocument();

  const searchInput = screen.getByPlaceholderText('Search by product name or category');
  fireEvent.change(searchInput, { target: { value: 'test' } });

  expect(axios.get).toHaveBeenCalledWith('https://api.example.com/products?page=1&search=test');
});
Enter fullscreen mode Exit fullscreen mode

Improved Complete Code

Based on the review comments, here’s a more robust, readable, and maintainable version of the code:

import React, { useState, useEffect } from 'react';
import { debounce } from 'lodash';
import axios from 'axios';
import SearchBar from './SearchBar';
import ProductItem from './ProductItem';
import Pagination from './Pagination';
import { API_BASE_URL } from '../constants/api';
import './ProductList.css';

function ProductList() {
  const [products, setProducts] = useState([]);
  const [currentPage, setCurrentPage] = useState(1);
  const [searchQuery, setSearchQuery] = useState('');
  const [error, setError] = useState(null);

  useEffect(() => {
    setError(null);
    const url = `${API_BASE_URL}/products?page=${currentPage}&search=${encodeURIComponent(searchQuery)}`;
    axios.get(url)
      .then(res => {
        setProducts(res.data);
      })
      .catch(() => {
        setError('Failed to fetch products. Please try again.');
      });
  }, [currentPage, searchQuery]);

  const handleSearch = debounce((value) => {
    setSearchQuery(value);
    setCurrentPage(1);
  }, 300);

  const handlePageChange = (newPage) => {
    if (newPage >= 1) {
      setCurrentPage(newPage);
    }
  };

  return (
    <div className="product-list__container">
      {error && <div className="product-list__error">{error}</div>}
      <SearchBar onSearch={handleSearch} />
      <div className="product-list__grid">
        {products.map(product => (
          <ProductItem key={product.id} product={product} />
        ))}
      </div>
      <Pagination
        currentPage={currentPage}
        onPageChange={handlePageChange}
      />
    </div>
  );
}

export default ProductList;
Enter fullscreen mode Exit fullscreen mode
import React from 'react';

function SearchBar({ onSearch }) {
  const handleChange = (event) => {
    onSearch(event.target.value);
  };

  return (
    <input
      type="text"
      onChange={handleChange}
      placeholder="Search by product name or category"
      className="product-list__search"
      aria-label="Search products"
    />
  );
}

export default SearchBar;
Enter fullscreen mode Exit fullscreen mode
import React from 'react';

function ProductItem({ product }) {
  const handleAddToCart = () => {
    alert(`Added ${product.name} to cart!`);
  };

  return (
    <div className="product-list__item">
      <img src={product.image} alt={product.name} />
      <h2>{product.name}</h2>
      <p>${product.price.toFixed(2)}</p>
      <button
        onClick={handleAddToCart}
        className="product-list__button"
        aria-label={`Add ${product.name} to cart`}
      >
        Add to cart
      </button>
    </div>
  );
}

export default ProductItem;
Enter fullscreen mode Exit fullscreen mode
import React from 'react';

function Pagination({ currentPage, onPageChange }) {
  return (
    <div className="product-list__pagination">
      <button
        onClick={() => onPageChange(currentPage - 1)}
        disabled={currentPage === 1}
        aria-label="Previous page"
      >
        Previous
      </button>
      <span>Page {currentPage}</span>
      <button
        onClick={() => onPageChange(currentPage + 1)}
        aria-label="Next page"
      >
        Next
      </button>
    </div>
  );
}

export default Pagination;
Enter fullscreen mode Exit fullscreen mode
.product-list__container {
  padding: 20px;
}
.product-list__grid {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
}
.product-list__item {
  width: 200px;
  border: 1px solid #ccc;
  padding: 10px;
  border-radius: 5px;
}
.product-list__item img {
  width: 100%;
  border-radius: 5px;
}
.product-list__button {
  background: #007bff;
  color: white;
  padding: 8px;
  border: none;
  border-radius: 3px;
  cursor: pointer;
}
.product-list__button:disabled {
  background: #ccc;
  cursor: not-allowed;
}
.product-list__search {
  padding: 10px;
  width: 100%;
  max-width: 300px;
  margin-bottom: 20px;
}
.product-list__error {
  color: red;
  margin-bottom: 10px;
}
.product-list__pagination {
  margin-top: 20px;
  display: flex;
  gap: 10px;
  align-items: center;
}
Enter fullscreen mode Exit fullscreen mode
export const API_BASE_URL = 'https://api.example.com';
Enter fullscreen mode Exit fullscreen mode

Highlights of the Improved Code

What makes the revised code better? Let’s summarize:

  • Clear Structure: Split into SearchBar, ProductItem, and Pagination components, each with a single responsibility, improving maintainability.
  • Enhanced Robustness: Added error handling, debouncing, and URL encoding for stability.
  • Improved Accessibility: Included alt attributes and ARIA properties for better user experience.
  • Standardized Styling: Used BEM naming for modular, conflict-free CSS.
  • Testability: The structure supports easier unit testing, simplifying future maintenance.

Code Review Best Practices

From this example, here are some best practices for team reference:

  • Focus on Priorities: Start with functionality, performance, and security, then address style and details.
  • Leverage Tools: Use ESLint, Prettier, and Stylelint to automate style checks, reducing manual review effort.
  • Write Clear Comments: Be specific and friendly, offering actionable suggestions. For example, “Add debouncing to reduce API calls” is better than “This has performance issues.”
  • Regular Retrospectives: Periodically review the team’s review process to identify slowdowns or vague comments.
  • Train Newcomers: Encourage new team members to participate in reviews, even just observing, to quickly learn team coding habits.

Common Issues and Solutions

Code reviews aren’t always smooth. Here’s how to handle common problems:

  • Review Delays: Some team members procrastinate. Set a deadline, e.g., 48 hours, with automated reminders.
  • Opinion Conflicts: If reviewers and submitters disagree, hold a quick meeting or involve a senior colleague to mediate.
  • Superficial Reviews: Some approve without thorough checks. Require comments or mandate checking specific aspects.
  • Newcomer Anxiety: Newbies may fear criticism. Foster an open culture, emphasizing reviews are for quality, not fault-finding.

Team Culture and Code Review

Code reviews are as much about team culture as they are about technology. A strong review culture builds team unity and code reliability. How to nurture it?

  • Mutual Respect: Use friendly tones in reviews, and encourage submitters to accept feedback graciously.
  • Continuous Learning: Treat reviews as learning opportunities, sharing best practices like, “This Hook is well-written; let’s adopt it.”
  • Incentives: Recognize thorough reviewers with praise or performance points to encourage participation.
  • Regular Training: Conduct code review workshops to teach writing quality code and providing constructive feedback, especially for newcomers.

Conclusion

Code reviews may take time, but they’re worth it. They make code more robust, projects smoother, and teams more cohesive. The product list component example shows that reviews go beyond finding bugs—they improve functionality, performance, accessibility, security, and standards. Hopefully, this article inspires you to approach code reviews—whether reviewing or being reviewed—with confidence and efficiency.

Next time you review code, remember: understand the code’s intent before critiquing, provide specific and friendly comments, and use tools to avoid repetitive work. Let’s write clean, maintainable code that spares future maintainers a few choice words!

Top comments (0)