Introduction to useState

What is useState?

useState is one of React's built-in hooks that allows function components to have their own state. Think of state as a component's memory - it's data that can change over time and affects what the component displays on screen.

Why Do We Need useState?

Without state, your components would be static and couldn't:

  • Respond to user clicks or inputs
  • Remember information between renders
  • Update what's on screen based on user actions
  • Store data fetched from an API

useState Made Simple

Imagine teaching a child to keep track of their toys:

  1. You give them a notepad with their toys listed (initial state)
  2. They can look at the notepad anytime to see what toys they have (reading state)
  3. When they get a new toy, they don't scribble on the notepad - they ask you for a fresh page with the updated list (updating state)
  4. You give them a completely new list that includes all their toys (re-rendering)

This is exactly how useState works in React!

Basic Syntax

import { useState } from "react";

function Counter() {
  // Declare a state variable named "count" with initial value of 0
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

Let's break this down:

  1. We import the useState hook from React
  2. We call useState(0) to create a state variable starting with value 0
  3. useState gives us back two things in an array:
    • The current state value (count)
    • A function to update that value (setCount)
  4. When the button is clicked, we call setCount(count + 1) to increase the count
  5. React then re-renders our component with the new count value

useState in Real Life

Here's a practical example - a simple task tracker:

import { useState } from "react";

function TaskTracker() {
  const [tasks, setTasks] = useState([]);
  const [newTask, setNewTask] = useState("");

  function addTask() {
    if (newTask.trim() === "") return;

    setTasks([...tasks, { id: Date.now(), text: newTask, done: false }]);
    setNewTask(""); // Clear input after adding
  }

  function toggleTask(taskId) {
    setTasks(tasks.map((task) => (task.id === taskId ? { ...task, done: !task.done } : task)));
  }

  return (
    <div>
      <h2>My Tasks</h2>

      <div>
        <input
          value={newTask}
          onChange={(e) => setNewTask(e.target.value)}
          placeholder="Enter a new task"
        />
        <button onClick={addTask}>Add Task</button>
      </div>

      <ul>
        {tasks.map((task) => (
          <li key={task.id} onClick={() => toggleTask(task.id)}>
            <span style={{ textDecoration: task.done ? "line-through" : "none" }}>{task.text}</span>
          </li>
        ))}
      </ul>
    </div>
  );
}

Key Rules for useState

  1. Always use the setter function to update state, never modify state directly

    // WRONG
    count = count + 1;
    
    // RIGHT
    setCount(count + 1);
    
  2. Treat state as immutable - never modify arrays or objects directly

    // WRONG
    const addItem = () => {
      tasks.push(newItem); // Modifying original array
      setTasks(tasks);
    };
    
    // RIGHT
    const addItem = () => {
      setTasks([...tasks, newItem]); // Creating a new array
    };
    
  3. State updates may not happen immediately - React might batch them for performance

    // This might not work as expected
    setCount(count + 1);
    console.log(count); // Still the old value!
    
  4. When updating state based on previous state, use the functional form

    // BETTER
    setCount((prevCount) => prevCount + 1);
    

Common useState Patterns

Toggle State (On/Off)

const [isOn, setIsOn] = useState(false);

// To toggle
const toggle = () => setIsOn((prevState) => !prevState);

Form Inputs

const [username, setUsername] = useState("");

// Update from input
<input value={username} onChange={(e) => setUsername(e.target.value)} />;

Loading States

const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState(null);

async function fetchData() {
  setIsLoading(true);
  try {
    const response = await fetch("https://api.example.com/data");
    const result = await response.json();
    setData(result);
  } catch (error) {
    console.error(error);
  } finally {
    setIsLoading(false);
  }
}

I apologize for missing the exercises section. Here are three exercises focused on learning useState:

Exercises

Exercise 1: Basic Counter

Objective: Create a counter component with buttons to increase, decrease, and reset the count.

Detailed Instructions:

  1. Create a new file called counter.tsx in your components folder.
  2. Import React and the useState hook.
  3. Create and export a functional component named Counter.
  4. Inside your component:
    • Create a state variable called count with an initial value of 0.
    • 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
  5. For each button, create an onClick handler that uses the setCount function to update the state.

Starter Code:

import { useState } from "react";

const Counter = () => {
  // Add your state declaration here

  return <div>{/* Add your UI elements here */}</div>;
};

export default Counter;

Exercise 2: Toggle Visibility

Objective: Create a component with a button that shows and hides content when clicked.

Detailed Instructions:

  1. Create a new file called toggle-content.tsx in your components folder.
  2. Import React and the useState hook.
  3. Create and export a functional component named ToggleContent.
  4. Inside your component:
    • Create a state variable called isVisible with an initial value of false.
    • Create a return statement with a div that contains:
      • A button that toggles the isVisible state when clicked
      • Some content (multiple paragraphs) that should only be visible when isVisible is true
  5. Use conditional rendering to show or hide the content based on the isVisible state.
  6. Make the button text dynamic - it should say "Show Content" when content is hidden and "Hide Content" when content is visible.

Starter Code:

import { useState } from "react";

const ToggleContent = () => {
  // Add your state declaration here

  return (
    <div>
      {/* Add your toggle button here */}

      {/* Add your conditional content here */}
    </div>
  );
};

export default ToggleContent;

Exercise 3: Form with Multiple Fields

Objective: Create a contact form that uses state to manage multiple input fields and form submission.

Detailed Instructions:

  1. Create a new file called contact-form.tsx in your components folder.
  2. Import React and the useState hook.
  3. Create and export a functional component named ContactForm.
  4. 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.
  5. 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.
  6. 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.

Starter Code:

import { useState } from "react";

interface FormData {
  name: string;
  email: string;
  message: string;
}

const ContactForm = () => {
  // Add your state declarations here

  // Add your event handlers here

  // Add conditional rendering here

  return (
    // Return either the form or the success message based on submission state
    <div>{/* Your component JSX goes here */}</div>
  );
};

export default ContactForm;

Remember

  • Use state only for values that change and affect rendering
  • Keep state as simple as possible
  • Use one state for each value that can change independently
  • When multiple values always change together, group them in an object