State is a JavaScript object that holds data which may change over time and affects what a component renders. Unlike props which are passed from parent components, state is managed internally by a component itself.
In function components, we add and use state with the useState hook:
import { useState } from"react";constCounter= () => {// 1. Declare a state variable named "count" with initial value of 0// 2. Get a function "setCount" to update this variableconst [count,setCount] =useState(0);return ( <div> <p>You clicked {count} times</p> <buttononClick={() =>setCount(count +1)}>Click me</button> </div> );};
Understanding useState
The useState hook:
const [state,setState] =useState(initialValue);
Takes an initial value for your state
Returns an array with exactly two items:
The current state value
A function to update that state value
We typically use array destructuring to give these items descriptive names
State updates may be batched or delayed
Multiple state updates in the same function may be batched together for performance.
State updates are not immediate
The state variable doesn't change right after calling the state update function:
consthandleClick= () => {setCount(count +1);console.log(count); // Still the old value!};
Functional Updates
When new state depends on the previous state, use the functional form of the state updater:
constCounter= () => {const [count,setCount] =useState(0);// Better approach when new state depends on old stateconstincrement= () => {setCount((prevCount) => prevCount +1); };// This doesn't work correctly with rapidly repeated clicksconstincrementMultiple= () => {setCount(count +1); // Both use same 'count' valuesetCount(count +1); // So this just sets it to the same value twice };// This works correctlyconstincrementMultipleBetter= () => {setCount((prevCount) => prevCount +1); // Uses latest valuesetCount((prevCount) => prevCount +1); // Also uses latest value };return ( <div> <p>Count: {count}</p> <buttononClick={increment}>Increment</button> <buttononClick={incrementMultipleBetter}>Increment Twice</button> </div> );};
Only move state up the tree when it truly needs to be shared.
2. Use derived values instead of additional state
// Bad: Using extra state that could be derivedconstCounter= () => {const [count,setCount] =useState(0);const [isPositive,setIsPositive] =useState(false);constincrement= () => {constnewCount= count +1;setCount(newCount);setIsPositive(newCount >0); // Unnecessary extra state update };return ( <div> <p>Count: {count}</p> <p>{isPositive ?"Positive":"Zero or negative"}</p> <buttononClick={increment}>Increment</button> </div> );};// Good: Calculate derived values during renderconstCounter= () => {const [count,setCount] =useState(0);// This is calculated during render, not stored in stateconstisPositive= count >0;return ( <div> <p>Count: {count}</p> <p>{isPositive ?"Positive":"Zero or negative"}</p> <buttononClick={() =>setCount(count +1)}>Increment</button> </div> );};
3. Group related state
If multiple state variables are always updated together, consider combining them:
Don't store information in state that can be computed from props or other state:
// Bad: Redundant stateconstUserProfile= ({ user }) => {// Don't do this! It creates a copy that won't update if user prop changesconst [userData,setUserData] =useState(user);return <div>{userData.name}</div>;};// Good: Use props directlyconstUserProfile= ({ user }) => {return <div>{user.name}</div>;};
Objective: Create a counter component with buttons to increase, decrease, and reset the count.
Detailed Instructions:
Create a new file called counter.tsx in your components folder.
Import React and the useState hook from the 'react' package.
Create and export a functional component named Counter.
Inside your component:
Create a state variable called count with an initial value of 0 using the useState hook.
Create a return statement with a div that contains:
An h2 element to display the current count value
A button labeled "Decrement" that decreases the count by 1 when clicked
A button labeled "Increment" that increases the count by 1 when clicked
A button labeled "Reset" that sets the count back to 0 when clicked
For each button, create an onClick handler that uses the setCount function to update the state.
Remember to export your component so it can be used in other files.
Hints:
Use the setCount function provided by useState to update the count.
For the "Decrement" button, use setCount(count - 1).
For the "Increment" button, use setCount(count + 1).
For the "Reset" button, use setCount(0).
Expected Result:
When rendered, your component should display the current count and three buttons. Clicking "Increment" should increase the count by 1, clicking "Decrement" should decrease it by 1, and clicking "Reset" should set it back to 0.
Exercise 2: Toggle Visibility
Objective: Create a component with a button that shows and hides content when clicked.
Detailed Instructions:
Create a new file called toggle-content.tsx in your components folder.
Import React and the useState hook from the 'react' package.
Create and export a functional component named ToggleContent.
Inside your component:
Create a state variable called isVisible with an initial value of false using the useState hook.
Create a return statement with a div that contains:
A button that toggles the isVisible state when clicked
Some content (multiple paragraphs or other elements) that should only be visible when isVisible is true
Use conditional rendering to show or hide the content based on the isVisible state.
Make the button text dynamic - it should say "Show Content" when content is hidden and "Hide Content" when content is visible.
Hints:
Use the logical AND operator (&&) for conditional rendering: {isVisible && <div>Content</div>}
To toggle a boolean state, use the NOT operator (!): setIsVisible(!isVisible)
Use a ternary operator for the button text: {isVisible ? 'Hide Content' : 'Show Content'}
Expected Result:
When rendered, your component should display only a button labeled "Show Content". When clicked, additional content should appear, and the button text should change to "Hide Content". Clicking again should hide the content and change the button text back to "Show Content".
Exercise 3: Form with Multiple Fields
Objective: Create a contact form that uses state to manage multiple input fields and form submission.
Detailed Instructions:
Create a new file called contact-form.tsx in your components folder.
Import React and the useState hook from the 'react' package.
Create and export a functional component named ContactForm.
Inside your component:
Create a state object called formData with properties for name, email, and message, all initialized as empty strings.
Create another state variable called submitted with an initial value of false.
Create a function called handleChange that updates the appropriate property in the formData state when any input changes.
Create a function called handleSubmit that prevents the default form submission, logs the form data, and sets submitted to true.
Create conditional rendering to show either:
The form with input fields for name, email, and message when submitted is false.
A success message and a button to submit another message when submitted is true.
Remember to:
Use the value attribute on each input to make it a controlled component.
Connect each input to the handleChange function.
Add proper labels for accessibility.
Include a submit button in the form.
Hints:
For object state updates, remember to spread the existing state: {...formData, [name]: value}
Use name attributes on inputs to identify which field to update in the handleChange function.
Use the required attribute on inputs for basic validation.
For the form submission handler, don't forget e.preventDefault() to stop the page from reloading.
Expected Result:
A form with three input fields (name, email, message) and a submit button. When the form is submitted, it should be replaced with a success message and a button to submit another message. Clicking that button should reset the form.
Exercise 4: Shopping List
Objective: Create a shopping list component that allows users to add, check off, and remove items.
Detailed Instructions:
Create a new file called shopping-list.tsx in your components folder.
Import React and the useState hook from the 'react' package.
Create and export a functional component named ShoppingList.
Inside your component:
Create a state array called items initialized as an empty array. Each item should eventually be an object with id, name, and completed properties.
Create a state variable called inputValue initialized as an empty string to track what the user types in the input field.
Create three functions:
handleAddItem: Adds a new item to the list when the user clicks the Add button.
handleToggleItem: Marks an item as completed or not completed when the user clicks the checkbox.
handleRemoveItem: Removes an item from the list when the user clicks the Remove button.
Create a user interface with:
An input field for typing new items
An "Add" button to add items to the list
A list of existing items, where each item has:
A checkbox to mark it as completed
The item name (crossed out when completed)
A "Remove" button to delete the item
If the list is empty, display a message saying "Your shopping list is empty."
Hints:
Generate unique IDs for new items with Date.now() or a similar method.
For the handleAddItem function, remember to:
Check if the input is empty (don't add empty items)
Create a new item object with id, name, and completed properties
Add the new item to the array using the spread operator
Clear the input field after adding
For the handleToggleItem function, use map to create a new array where the target item has its completed property toggled.
For the handleRemoveItem function, use filter to create a new array without the item to remove.
Use conditional styling with textDecoration: item.completed ? 'line-through' : 'none' to show completed items as crossed out.
Expected Result:
A fully functional shopping list where users can add new items, mark items as completed (which shows them crossed out), and remove items from the list. The input field should clear after adding an item.
State management is a fundamental concept in React that allows you to build dynamic, interactive user interfaces. By understanding how to properly use useState, you can:
Create components that respond to user input
Track values that change over time
Update the UI automatically when data changes
Share data between components through props
Remember these key principles:
Always use the setter function to update state
Treat state as immutable - create new objects/arrays instead of modifying existing ones
Keep state as simple as possible and derive values where you can
Lift state up when multiple components need access to the same data
In the next section, we'll explore more advanced patterns and hooks for managing complex state and side effects in your React applications.