Phase 2: Folder Component

Understanding the Problem

In this phase, we'll refactor the Folder component from a class component to a function component. The Folder component represents a more complex challenge than the App component because it:

This phase will teach us how to handle props in function components and how component composition works with hooks.

George Polya's Problem-Solving Method

Step 1: Understand the Problem

Let's analyze the original Folder class component to understand its functionality:


// Folder.jsx
import React from 'react';

const Headers = ({ titles, currentTab, selectTab }) => {
  const handleClick = (e) => {
    const idx = parseInt(e.target.id, 10);
    selectTab(idx);
  }

  const tabs = titles.map((title, idx) => {
    const headerClass = (idx === currentTab) ? 'active' : '';

    return (
      <li
        key={idx}
        id={idx}
        onClick={handleClick}
        className={headerClass}
      >
        {title}
      </li>
    );
  });
  
  return (
    <ul className='tab-header'>
      {tabs}
    </ul>
  );
}

class Folder extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      currentTab: 0
    };
  }
  
  selectTab = (num) => {
    this.setState({ currentTab: num });
  }
  
  render() {
    const folder = this.props.folders[this.state.currentTab];
    const titles = this.props.folders.map((folder) => folder.title);
    
    return (
      <section className="tabs-section">
        <h1>Tabs</h1>
        <div className='tabs'>
          <Headers
            titles={titles}
            currentTab={this.state.currentTab}
            selectTab={this.selectTab}
          />
          <div className='tab-content'>
            {folder.content}
          </div>
        </div>
      </section>
    );
  }
}

export default Folder;
            

Analyzing this code, we can see that:

Step 2: Devise a Plan

  1. Convert the Folder class declaration to a function declaration
  2. Use object destructuring in the function parameters to directly access the folders prop
  3. Replace this.state.currentTab with a useState hook
  4. Convert the selectTab method to a regular function
  5. Update references to this.state and this.props in the JSX
  6. Remove the render method and return JSX directly

Step 3: Carry Out the Plan

1. Component Declaration and Props

We'll change the class declaration to a function declaration and use object destructuring for props:


// From
class Folder extends React.Component {
  // Accessing props via this.props.folders
  
// To
function Folder({ folders }) {
  // Directly accessing folders prop
            

This destructuring syntax is a clean way to extract specific props directly in the function signature, making it immediately clear what props the component expects.

2. State Management with useState

We'll replace the class state with useState:


// From
constructor(props) {
  super(props);
  this.state = {
    currentTab: 0
  };
}

// To
const [currentTab, setCurrentTab] = useState(0);
            

Don't forget to import useState at the top of your file:

import React, { useState } from 'react';

3. Converting Methods

We'll convert the selectTab method to a regular function:


// From
selectTab = (num) => {
  this.setState({ currentTab: num });
}

// To
const selectTab = (num) => {
  setCurrentTab(num);
};
            

4. Updating References

We'll update the variable access and JSX references:


// From
const folder = this.props.folders[this.state.currentTab];
const titles = this.props.folders.map((folder) => folder.title);

// To
const folder = folders[currentTab];
const titles = folders.map((folder) => folder.title);
            

// From
<Headers
  titles={titles}
  currentTab={this.state.currentTab}
  selectTab={this.selectTab}
/>

// To
<Headers
  titles={titles}
  currentTab={currentTab}
  selectTab={selectTab}
/>
            

5. Putting It All Together

Here's the complete refactored Folder component:


// Folder.jsx (Refactored)
import React, { useState } from 'react';

const Headers = ({ titles, currentTab, selectTab }) => {
  const handleClick = (e) => {
    const idx = parseInt(e.target.id, 10);
    selectTab(idx);
  }

  const tabs = titles.map((title, idx) => {
    const headerClass = (idx === currentTab) ? 'active' : '';

    return (
      <li
        key={idx}
        id={idx}
        onClick={handleClick}
        className={headerClass}
      >
        {title}
      </li>
    );
  });
  
  return (
    <ul className='tab-header'>
      {tabs}
    </ul>
  );
}

function Folder({ folders }) {
  // Replace this.state with useState
  const [currentTab, setCurrentTab] = useState(0);
  
  // Convert selectTab method to a regular function
  const selectTab = (num) => {
    setCurrentTab(num);
  };
  
  // Get the current folder and titles
  const folder = folders[currentTab];
  const titles = folders.map((folder) => folder.title);
  
  // Return what was previously in the render method
  return (
    <section className="tabs-section">
      <h1>Tabs</h1>
      <div className='tabs'>
        <Headers
          titles={titles}
          currentTab={currentTab}
          selectTab={selectTab}
        />
        <div className='tab-content'>
          {folder.content}
        </div>
      </div>
    </section>
  );
}

export default Folder;
            

Step 4: Look Back and Review

Let's verify that our refactored component maintains the same functionality as the original:

Run the tests to confirm everything is working as expected:

npm test

All tests for the Folder component should pass, confirming that our refactoring was successful.

Component Composition and Props in Function Components

Working with Props

One of the key differences between class and function components is how they handle props:

In Class Components:

Props are accessed through this.props, which is an object containing all the properties passed to the component:

class MyComponent extends React.Component {
  render() {
    const { prop1, prop2 } = this.props; // Destructuring within methods
    return <div>{this.props.prop1}, {prop2}</div>;
  }
}

In Function Components:

Props are received as the first parameter to the function, and can be destructured directly in the function signature:

function MyComponent({ prop1, prop2 }) {
  return <div>{prop1}, {prop2}</div>;
}

This is more concise and eliminates the need for this, which is often a source of confusion in React.

Component Composition

Our Folder component demonstrates a common pattern in React: component composition. The Folder component uses a Headers component to render the tab headers, creating a clean separation of concerns:

This pattern works the same way in both class and function components, but function components often make the data flow clearer because:

Real-World Applications

The tabs pattern we've implemented is extremely common in web applications and can be found in many interfaces:

The pattern can be extended to more complex interactions:

By understanding how to implement this pattern with function components and hooks, you're building a foundation for creating many types of interactive interfaces.

Comparison: Class vs. Function Component Props

Let's compare how props are handled in class vs. function components side by side:

Feature Class Component Function Component
Accessing props this.props.propName propName (after destructuring)
Default props Component.defaultProps = { prop: value } Default parameters: function Component({ prop = value })
Props destructuring Inside methods: const { prop1, prop2 } = this.props; In function parameters: function Component({ prop1, prop2 })
Prop types Component.propTypes = { prop: PropTypes.string } Same: Component.propTypes = { prop: PropTypes.string }
Renaming props const { longPropName: shortName } = this.props; function Component({ longPropName: shortName })
Rest props More complex: requires separate destructuring and rest Simple: function Component({ prop1, ...restProps })

As you can see, function components generally provide a more concise and straightforward way to work with props, leveraging standard JavaScript features like destructuring and default parameters.