Understanding the Problem
In this phase, we'll refactor the App class component to a function component. This is an excellent starting point as it's relatively simple but introduces the fundamental concept of state management with hooks.
The key aspects we need to address in the App component are:
- Converting the class declaration to a function declaration
- Converting state management from class-based approach to hooks-based approach
- Converting methods to regular functions
The App component is the root component of our application. It's responsible for rendering all the other widget components and controlling the visibility of the Clock component through a toggle button.
George Polya's Problem-Solving Method
Step 1: Understand the Problem
Let's first look at the original App class component to understand its functionality:
// App.jsx
import React from 'react';
import Clock, { ClockToggle } from './components/Clock';
import Folder from './components/Folder';
import Weather from './components/Weather';
import Autocomplete from './components/Autocomplete';
const names = [
'Abba',
'Barbara',
'Barney',
'Jeff',
'Jenny',
'Sally',
'Sarah',
'Xander'
];
const folders = [
{ title: 'one', content: 'I am the first' },
{ title: 'two', content: 'Second folder here' },
{ title: 'three', content: 'Third folder here' }
];
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
showClock: true
};
}
toggleClock = () => this.setState({ showClock: !this.state.showClock });
render () {
return (
<div className="widgets">
<Folder folders={folders} />
<Weather />
<ClockToggle toggleClock={this.toggleClock} />
{this.state.showClock && <Clock />}
<Autocomplete names={names} />
</div>
);
}
}
export default App;
Examining this code, we can see that:
- The component has one state variable:
showClock(initialized totrue) - It has one method:
toggleClock, which toggles theshowClockstate - The
rendermethod returns JSX that conditionally renders theClockcomponent based onshowClock - It passes the
toggleClockmethod to theClockTogglecomponent - It passes props to the
FolderandAutocompletecomponents
Step 2: Devise a Plan
- Change the class declaration to a function declaration
- Replace
constructorandthis.statewith theuseStatehook - Convert the
toggleClockmethod to a regular function - Remove the
render()method and directly return JSX - Update any references to
this.stateorthis.toggleClock
Step 3: Carry Out the Plan
Let's implement our plan step by step:
1. Change the Component Declaration
We'll change from a class that extends React.Component to a regular JavaScript function:
// From
class App extends React.Component {
// ...
}
// To
function App() {
// ...
}
2. Import useState and Replace State Management
We need to import the useState hook and use it to replace the class state:
// Add this import
import React, { useState } from 'react';
// Replace constructor and this.state with useState
const [showClock, setShowClock] = useState(true);
The useState hook takes an initial value (true in this case) and returns an array with two elements: the current state value and a function to update it. We're using array destructuring to assign these to variables.
3. Convert the toggleClock Method
We'll convert the class method to a regular function and use the setShowClock function:
// From
toggleClock = () => this.setState({ showClock: !this.state.showClock });
// To
const toggleClock = () => setShowClock(!showClock);
4. Update JSX References
Now we need to update references in the returned JSX:
// From
<ClockToggle toggleClock={this.toggleClock} />
{this.state.showClock && <Clock />}
// To
<ClockToggle toggleClock={toggleClock} />
{showClock && <Clock />}
5. Remove the render Method
Finally, we'll remove the render() method and return the JSX directly from the function component:
// From
render () {
return (
<div className="widgets">
// ...
</div>
);
}
// To
return (
<div className="widgets">
// ...
</div>
);
Putting it all together, our refactored component looks like this:
// App.jsx (Refactored)
import React, { useState } from 'react';
import Clock, { ClockToggle } from './components/Clock';
import Folder from './components/Folder';
import Weather from './components/Weather';
import Autocomplete from './components/Autocomplete';
const names = [
'Abba',
'Barbara',
'Barney',
'Jeff',
'Jenny',
'Sally',
'Sarah',
'Xander'
];
const folders = [
{ title: 'one', content: 'I am the first' },
{ title: 'two', content: 'Second folder here' },
{ title: 'three', content: 'Third folder here' }
];
function App() {
// Replace this.state with useState
const [showClock, setShowClock] = useState(true);
// Convert toggleClock method to a regular function
const toggleClock = () => setShowClock(!showClock);
// Return what was previously in the render method
return (
<div className="widgets">
<Folder folders={folders} />
<Weather />
<ClockToggle toggleClock={toggleClock} />
{showClock && <Clock />}
<Autocomplete names={names} />
</div>
);
}
export default App;
Step 4: Look Back and Review
Let's verify that our refactored component maintains the same functionality as the original:
- We've preserved the initial state:
showClockis still initialized totrue - The
toggleClockfunction still toggles theshowClockvalue - The
ClockTogglecomponent still receives thetoggleClockfunction as a prop - The
Clockcomponent is still conditionally rendered based onshowClock - All the other components are still rendered with their respective props
Run the tests to make sure everything is working as expected:
npm test
All tests for the App component should pass, confirming that our refactoring was successful.
Deep Dive: Understanding useState
Let's take a closer look at the useState hook, which is central to state management in function components.
How useState Works
The useState hook is a function that you call inside your component to add state to it. It returns a pair of values: the current state and a function that updates it.
const [state, setState] = useState(initialValue);
When you call useState, you're telling React that:
- You want this component to remember something (the state)
- The initial value of that state is what you passed as the argument
- You'll use the first element of the returned array to read the state
- You'll use the second element of the returned array to change the state
Class Component vs. Function Component State
Let's compare how state is handled in both approaches:
Class Component:
- State is always an object:
this.state = { key1: value1, key2: value2, ... } - State is updated by merging:
this.setState({ key1: newValue })only updateskey1, leavingkey2unchanged - Access state via
this.state.key - State is defined once in the constructor
Function Component with useState:
- State can be any type: object, string, number, array, etc.
- State updates replace the value entirely, not merge
- Access state via the variable name:
showClock - Can have multiple independent state variables
- Each state variable has its own setter function
Advantages of useState
The useState approach offers several advantages:
- Simplicity: State can be simple primitives instead of always being objects
- Organization: Related state can be grouped together logically
- Readability: Separate
setXfunctions clearly indicate what state is being updated - Avoids "this" confusion: No need to worry about binding methods or using arrow functions
- Code reuse: State logic can be extracted into custom hooks and reused across components
Real-World Applications
This pattern of replacing class state with useState is extremely common in React applications. As you migrate legacy React code or build new features, you'll frequently use this technique to manage component state.
Some examples of where you might use useState in real applications:
- Form inputs (text fields, checkboxes, radio buttons, etc.)
- UI state (open/closed modals, selected tabs, etc.)
- Toggle states (on/off, visible/hidden, etc.)
- Count values (number of items, pagination indexes, etc.)
- Simple data that doesn't require complex transformations