Skip to content

Component Organization

Keep components small and reusable

Single Responsibility Principle

  • Definition: Each component should have one specific responsibility or purpose.
  • Implementation: Break down functionality so that each component performs a single, distinct task. For example:
    • A button component should handle only the rendering of the button, not complex logic.
    • Separate a form into multiple components: one for the form container, one for input fields, and another for the submit button.

Reusability

  • Definition: Create components that can be used in multiple parts of your application without modification.
  • Implementation:
    • Accept props for customization:

      jsx
      const Button = ({ label, onClick, style }) => (
        <button onClick={onClick} style={style}>
          {label}
        </button>
      );

      This button can now be reused with different label, onClick, and style values.

    • Avoid hardcoding values: Don’t include data that is specific to a particular page or context inside reusable components. Pass data as props or through context.

Component Composition

  • Definition: Build complex components by combining smaller ones.

  • Implementation:

    • Use smaller, focused components to create large components:
    jsx
     const Input = ({ type, placeholder, value, onChange }) => (
       <input type={type} placeholder={placeholder} value={value} onChange={onChange} />
     );
    
     const SubmitButton = ({ onClick }) => <Button label="Submit" onClick={onClick} />;
    
     const Form = () => (
       <form>
         <Input type="text" placeholder="Name" onChange={handleInputChange} />
         <SubmitButton onClick={handleSubmit} />
       </form>
     );

Container vs Presentational Components

  • Definition: Separate components into "container" components (manage state, logic) and "presentational" components (display data).
  • Implementations:
    • Container components:

      • Manage state and logic.
      • Pass data and functions as props to presentational components.
      • Should not contain any JSX for rendering.
      jsx
        const UserListContainer = () => {
        const [users, setUsers] = useState([]);
      
        useEffect(() => {
          fetch('/api/users').then((res) => res.json()).then(setUsers);
        }, []);
      
        return <UserList users={users} />;
      };
    • Presentational components:

      • Receive data and functions as props.
      • Focus on rendering UI based on props.
      • Should not manage state or logic.
      jsx
      const UserList = ({ users }) => (
        <ul>
        {users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
      );

Folder Structure

  • Definition: Organize components by feature or domain to encourage reusability and readability.

  • Implementation:

    • Group by feature:

      • Create a folder for each feature or domain.
      • Place all components, styles, and tests related to that feature in the same folder.
      src/
      ├── components/
      │   ├── Button/
      │   │   ├── Button.js
      │   │   ├── Button.css
      │   │   └── Button.test.js
      │   ├── Form/
      │   │   ├── Form.js
      │   │   ├── Form.css
      │   │   └── Form.test.js
      │   └── ...
    • Shared components:

      • Create a components folder for shared components that are used across multiple features.
      • Place components that are not specific to a feature in this folder.
      src/
      ├── components/
      │   ├── Button/
      │   │   ├── Button.js
      │   │   ├── Button.css
      │   │   └── Button.test.js
      │   ├── Form/
      │   │   ├── Form.js
      │   │   ├── Form.css
      │   │   └── Form.test.js
      │   └── ...

Avoid Over-Engineering

  • Definition: Ensure components are not too abstract or generic.
  • Implementation:
    • YAGNI (You Aren’t Gonna Need It):
      • Only add complexity when it’s necessary.
      • Avoid creating abstractions or patterns that are not currently needed.
    • Avoid premature optimization:
      • Optimize components for readability and maintainability first.
      • Only optimize for performance when necessary.

Write Tests for Reusability

  • Definition: Ensure your components work in different scenarios.
  • Implementation: Use a testing library like React Testing Library:
jsx
  test('renders Button with correct label', () => {
    render(<Button label="Click Me" />);
    expect(screen.getByText('Click Me')).toBeInTheDocument();
  });

Use functional components and hooks whenever possible

Why Functional Components and Hooks?

Functional components with hooks have become the standard in React development for several reasons:

  • Simpler syntax:
    • Functional components are more concise and easier to read compared to class components.
  • Hooks for State and Side Effects:
    • Hooks like useState and useEffect provide a clean way to handle state and lifecycle methods without needing a class.
  • Performance Benefits:
    • Functional components with React.memo and hooks like useCallback and useMemo can optimize performance.
  • Future-Proof
    • React is moving towards functional components and hooks as the preferred standard.

Functional Components Over Class Components

  • Definition:

    • Functional components are JavaScript functions that take props as input and return JSX.
    • Replace complex class-based components with simpler functional alternatives.
  • Implementation:

    Class Component:

    jsx
      class Welcome extends React.Component {
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    }

    Functional Component:

    jsx
    const Welcome = ({ name }) => {
      return <h1>Hello, {name}</h1>;
    };

Use Hooks for State Management

  • Definition: Hooks like useState and useReducer allow you to add state to functional components without the need for classes.

  • Implementation:

    jsx
      const Counter = () => {
      const [count, setCount] = React.useState(0);
    
      const increment = () => setCount((prevCount) => prevCount + 1);
    
      return (
        <div>
          <p>Count: {count}</p>
          <button onClick={increment}>Increment</button>
        </div>
      );
    };

This replaces the this.state and this.setState syntax from class components with a simpler and more intuitive approach.

Separation of concerns