Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
menu
  1. Home
  2. /Web Development
  3. /React
  4. /State Best Practices
Your Progress0%
0 of 42 completed

React Topics

Getting Started

  • What is React?
  • React vs Vanilla JavaScript
  • Setting Up Your Environment
  • Your First React App

JSX Fundamentals

  • Introduction to JSX
  • JSX Expressions and Variables
  • Conditional Rendering
  • Lists and Keys
  • Styling in JSX

Components Basics

  • Function Components
  • Props
  • Children Prop
  • Props Destructuring
  • Component Composition

State Management

  • Introduction to State
  • useState Hook
  • State Updates and Re-renders
  • Multiple State Variables
  • State Best Practices

Events and Interactivity

  • Handling Events
  • Event Objects
  • Forms in React
  • Form Validation

Side Effects and Data

  • Introduction to useEffect
  • Data Fetching
  • Cleanup Functions
  • Dependency Arrays

Advanced Hooks

  • useRef Hook
  • useContext Hook
  • useMemo Hook
  • useCallback Hook

React Patterns

  • Custom Hooks
  • Higher-Order Components
  • Render Props Pattern

React Router

  • Introduction to React Router
  • Routes and Navigation
  • Dynamic Routes and Parameters

Best Practices

  • Component Organization
  • Performance Optimization
  • Error Handling
  • Testing React Components

Real World Project

  • Building a Complete App

State Best Practices

Professional patterns for managing state

You now know how to use state, update it, and organize it. But there's a difference between code that works and code that's professional. In this lesson, you'll learn the best practices that separate beginner code from production-ready code: immutability patterns, where to place state in your component tree, when to lift state up, and advanced techniques that will make you a better React developer. Let's level up your state management skills! 🚀

Immutability: The Golden Rule

The most important rule of React state: never mutate state directly. Always create new values.

Why Immutability Matters

why-immutability.jsx
function TodoList() {
  const [todos, setTodos] = React.useState(['Task 1', 'Task 2'])
  
  // ❌ Wrong - mutating state directly
  const addTodoBroken = (task) => {
    todos.push(task)  // Mutates the array
    setTodos(todos)   // React doesn't see the change!
    // Component doesn't re-render because todos is the same array object
  }
  
  // ✅ Correct - creating new array
  const addTodo = (task) => {
    setTodos([...todos, task])  // New array
    // React sees new array, component re-renders
  }
  
  return (
    <div>
      {todos.map((todo, i) => <p key={i}>{todo}</p>)}
      <button onClick={() => addTodoBroken('Task 3')}>
        Add (Broken)
      </button>
      <button onClick={() => addTodo('Task 3')}>
        Add (Works)
      </button>
    </div>
  )
}

// React uses Object.is() to compare old and new state
// If they're the same object, React skips re-render
// Immutability ensures React always sees the change

Benefits of Immutability:

  • React can detect changes reliably
  • Enables performance optimizations (React.memo, etc.)
  • Easier to track state history (undo/redo)
  • Prevents subtle bugs from shared references
  • Makes code more predictable and testable

Immutable Update Patterns

Arrays: Common Operations

immutable-arrays.jsx
function TodoApp() {
  const [todos, setTodos] = React.useState([
    { id: 1, text: 'Learn React', done: false },
    { id: 2, text: 'Build app', done: false }
  ])
  
  // ✅ Add item
  const addTodo = (text) => {
    const newTodo = { id: Date.now(), text, done: false }
    setTodos([...todos, newTodo])
    // Or: setTodos(prev => [...prev, newTodo])
  }
  
  // ✅ Remove item
  const removeTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id))
  }
  
  // ✅ Update item
  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, done: !todo.done } : todo
    ))
  }
  
  // ✅ Replace all
  const clearCompleted = () => {
    setTodos(todos.filter(todo => !todo.done))
  }
  
  // ✅ Insert at position
  const insertAt = (index, text) => {
    const newTodo = { id: Date.now(), text, done: false }
    setTodos([
      ...todos.slice(0, index),
      newTodo,
      ...todos.slice(index)
    ])
  }
}

Objects: Common Operations

immutable-objects.jsx
function UserProfile() {
  const [user, setUser] = React.useState({
    name: 'Alice',
    age: 25,
    email: 'alice@example.com',
    settings: {
      theme: 'light',
      notifications: true
    }
  })
  
  // ✅ Update single property
  const updateName = (name) => {
    setUser({ ...user, name })
  }
  
  // ✅ Update multiple properties
  const updateInfo = (name, email) => {
    setUser({ ...user, name, email })
  }
  
  // ✅ Update nested property
  const updateTheme = (theme) => {
    setUser({
      ...user,
      settings: {
        ...user.settings,
        theme
      }
    })
  }
  
  // ✅ Toggle boolean
  const toggleNotifications = () => {
    setUser({
      ...user,
      settings: {
        ...user.settings,
        notifications: !user.settings.notifications
      }
    })
  }
  
  // ✅ Remove property
  const removeEmail = () => {
    const { email, ...rest } = user
    setUser(rest)
  }
}

📝 Spread Operator Cheatsheet

Arrays:
[...arr, newItem] - Add to end
[newItem, ...arr] - Add to start
arr.filter(condition) - Remove items
arr.map(transform) - Update items

Objects:
{ ...obj, key: value } - Update/add property
{ ...obj, nested: { ...obj.nested, key: value } } - Update nested

Where to Place State

One of the most important decisions: which component should own the state?

Principle: Keep State as Local as Possible

local-state.jsx
// ❌ Bad - state in parent but only used by child
function App() {
  const [isModalOpen, setIsModalOpen] = React.useState(false)
  
  return (
    <div>
      <Header />
      <MainContent />
      <Modal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />
    </div>
  )
}

// ✅ Good - state in the component that uses it
function Modal() {
  const [isOpen, setIsOpen] = React.useState(false)
  
  return (
    <>
      <button onClick={() => setIsOpen(true)}>Open Modal</button>
      {isOpen && (
        <div className="modal">
          <button onClick={() => setIsOpen(false)}>Close</button>
        </div>
      )}
    </>
  )
}

function App() {
  return (
    <div>
      <Header />
      <MainContent />
      <Modal />  {/* State stays local */}
    </div>
  )
}

When to Lift State Up

Lift state to the nearest common ancestor when multiple components need to share it:

lift-state-up.jsx
// ❌ Problem - SearchBar and Results can't communicate
function SearchBar() {
  const [query, setQuery] = React.useState('')
  // How does Results get the query?
}

function Results() {
  // Need the query to filter results
}

function App() {
  return (
    <>
      <SearchBar />
      <Results />
    </>
  )
}

// ✅ Solution - lift state to common parent
function App() {
  const [query, setQuery] = React.useState('')
  
  return (
    <>
      <SearchBar query={query} onQueryChange={setQuery} />
      <Results query={query} />
    </>
  )
}

function SearchBar({ query, onQueryChange }) {
  return (
    <input
      value={query}
      onChange={e => onQueryChange(e.target.value)}
    />
  )
}

function Results({ query }) {
  // Can now use query to filter results
  return <div>Results for: {query}</div>
}

State Placement Rules:

  1. Start with state in the component that uses it
  2. If another component needs it, lift to common parent
  3. If state is used across many components, consider Context
  4. Keep state as close as possible to where it's used

Don't Store Derived State

Calculate values from existing state instead of storing them:

derived-state.jsx
// ❌ Bad - storing derived values
function ShoppingCart() {
  const [items, setItems] = React.useState([])
  const [total, setTotal] = React.useState(0)
  const [itemCount, setItemCount] = React.useState(0)
  
  const addItem = (item) => {
    setItems([...items, item])
    setTotal(total + item.price)      // Easy to forget!
    setItemCount(itemCount + 1)       // Can get out of sync!
  }
}

// ✅ Good - calculate derived values
function ShoppingCart() {
  const [items, setItems] = React.useState([])
  
  // Calculated on every render
  const total = items.reduce((sum, item) => sum + item.price, 0)
  const itemCount = items.length
  
  const addItem = (item) => {
    setItems([...items, item])
    // total and itemCount automatically update!
  }
}

// Don't worry about performance - these calculations are fast
// React is optimized for this pattern

When Calculation is Expensive

expensive-calculation.jsx
// If calculation is truly expensive, use useMemo (advanced)
function DataTable({ data }) {
  const [sortBy, setSortBy] = React.useState('name')
  
  // ❌ Without optimization - sorts on every render
  const sortedData = [...data].sort((a, b) => {
    // Expensive sorting logic...
  })
  
  // ✅ With useMemo - only sorts when data or sortBy changes
  const sortedData = React.useMemo(() => {
    return [...data].sort((a, b) => {
      // Expensive sorting logic...
    })
  }, [data, sortBy])
  
  // But don't use useMemo unless you have a performance problem!
}

State Initialization

Lazy Initialization

lazy-init.jsx
// ❌ Expensive function runs on every render
function App() {
  const [data, setData] = React.useState(expensiveCalculation())
  // expensiveCalculation() runs even during re-renders!
}

// ✅ Function form - only runs once
function App() {
  const [data, setData] = React.useState(() => expensiveCalculation())
  // expensiveCalculation() only runs on initial render
}

// Real example
function UserProfile() {
  // ❌ Bad - localStorage read on every render
  const [theme, setTheme] = React.useState(localStorage.getItem('theme') || 'light')
  
  // ✅ Good - localStorage read only once
  const [theme, setTheme] = React.useState(() => {
    return localStorage.getItem('theme') || 'light'
  })
}

Initial State from Props

init-from-props.jsx
// ⚠️ Common pattern but be careful
function EditableField({ initialValue }) {
  const [value, setValue] = React.useState(initialValue)
  
  // Problem: if initialValue prop changes, state doesn't update!
  
  return (
    <input
      value={value}
      onChange={e => setValue(e.target.value)}
    />
  )
}

// ✅ If you want to sync with prop changes, use a key
function Parent() {
  const [user, setUser] = React.useState({ name: 'Alice' })
  
  return (
    <EditableField
      key={user.id}  // New key forces component to remount
      initialValue={user.name}
    />
  )
}

// ✅ Or use useEffect to sync (we'll learn this later)
function EditableField({ initialValue }) {
  const [value, setValue] = React.useState(initialValue)
  
  React.useEffect(() => {
    setValue(initialValue)
  }, [initialValue])
  
  return <input value={value} onChange={e => setValue(e.target.value)} />
}

Controlled vs Uncontrolled Components

Controlled Components (Recommended)

controlled.jsx
// ✅ Controlled - React state is the "single source of truth"
function ControlledInput() {
  const [value, setValue] = React.useState('')
  
  return (
    <input
      value={value}
      onChange={e => setValue(e.target.value)}
    />
  )
}

// Benefits:
// - React state always matches input value
// - Easy to validate, transform, or clear
// - Can disable/enable based on other state
// - Predictable and testable

Uncontrolled Components

uncontrolled.jsx
// ⚠️ Uncontrolled - DOM is the source of truth
function UncontrolledInput() {
  const inputRef = React.useRef()
  
  const handleSubmit = () => {
    console.log(inputRef.current.value)  // Read from DOM
  }
  
  return (
    <>
      <input ref={inputRef} defaultValue="" />
      <button onClick={handleSubmit}>Submit</button>
    </>
  )
}

// Use uncontrolled only when:
// - Working with file inputs (can't be controlled)
// - Integrating with non-React code
// - Performance is critical and you don't need instant updates

// Otherwise, prefer controlled components!

Resetting State

Reset with Key Prop

reset-key.jsx
function App() {
  const [selectedUser, setSelectedUser] = React.useState(users[0])
  
  return (
    <div>
      <select onChange={e => setSelectedUser(users[e.target.value])}>
        {users.map((user, i) => (
          <option key={user.id} value={i}>{user.name}</option>
        ))}
      </select>
      
      {/* Key changes when user changes, form resets! */}
      <UserForm key={selectedUser.id} user={selectedUser} />
    </div>
  )
}

// When key changes, React destroys old component and creates new one
// All state is reset automatically

Reset with Callback

reset-callback.jsx
function Form() {
  const [formData, setFormData] = React.useState({
    name: '',
    email: '',
    message: ''
  })
  
  const handleSubmit = (e) => {
    e.preventDefault()
    console.log(formData)
    
    // Reset form after submit
    setFormData({
      name: '',
      email: '',
      message: ''
    })
  }
  
  const handleReset = () => {
    setFormData({
      name: '',
      email: '',
      message: ''
    })
  }
}

Interactive Demo

ReactPlayground.jsx
Code Editor
Preview

💡 Tip: Edit the code above and click "Run" to see your changes

Common State Pitfalls

1. Using State for Everything

pitfall-overuse.jsx
// ❌ Bad - storing unnecessary state
function ProductCard({ product }) {
  const [name, setName] = React.useState(product.name)
  const [price, setPrice] = React.useState(product.price)
  const [discountedPrice, setDiscountedPrice] = React.useState(
    product.price * 0.9
  )
  
  // All of this should just come from props!
}

// ✅ Good - use props directly, calculate what you need
function ProductCard({ product }) {
  const discountedPrice = product.price * 0.9
  
  return (
    <div>
      <h3>{product.name}</h3>
      <p>Was: ${product.price}</p>
      <p>Now: ${discountedPrice}</p>
    </div>
  )
}

2. Stale State in Callbacks

pitfall-stale.jsx
// ❌ Problem - stale state in timeout
function Counter() {
  const [count, setCount] = React.useState(0)
  
  const handleClick = () => {
    setCount(count + 1)
    
    setTimeout(() => {
      console.log(count)  // Logs old value!
    }, 3000)
  }
}

// ✅ Solution 1 - use function form
function Counter() {
  const [count, setCount] = React.useState(0)
  
  const handleClick = () => {
    setCount(prev => {
      const newCount = prev + 1
      
      setTimeout(() => {
        console.log(newCount)  // Logs correct value!
      }, 3000)
      
      return newCount
    })
  }
}

// ✅ Solution 2 - use useEffect (we'll learn this later)

3. Too Many Re-renders

pitfall-rerenders.jsx
// ❌ Infinite loop!
function Example() {
  const [count, setCount] = React.useState(0)
  
  setCount(count + 1)  // This runs during render!
  // Causes re-render, which runs this again, infinite loop
  
  return <div>{count}</div>
}

// ✅ Correct - update in event handler
function Example() {
  const [count, setCount] = React.useState(0)
  
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  )
}

Key Takeaways

  • Never mutate state - always create new values
  • Use spread operator for immutable updates
  • Keep state as local as possible
  • Lift state up to common ancestor when shared
  • Calculate derived values, don't store them
  • Use lazy initialization for expensive calculations
  • Prefer controlled components for forms
  • Use key prop to reset component state
  • Use function form when new state depends on previous
  • Don't call setState during render
  • Avoid storing props in state (they can get out of sync)
  • Start simple, optimize only when needed
  • Immutability enables React's performance optimizations

What's Next?

Congratulations! You've completed the State Management section. You now understand:

  • What state is and why it matters
  • How to use the useState hook
  • How state updates trigger re-renders
  • When to split or combine state
  • Professional state management best practices

You have the foundation for building truly interactive React applications! But there's so much more to learn:

  • Events - Handling user interactions properly
  • Side Effects - API calls, timers, subscriptions
  • Context - Sharing state across many components
  • Advanced Hooks - useReducer, useMemo, useCallback
  • And much more!

Keep going - you're building real React skills! 🎉

Learning professional state management patterns in React!

Previous
Multiple State Variables
Next
Handling Events

Never Miss a New React Tutorial

Join 2,000+ developers learning React step-by-step. Get new tutorials, tips, and exclusive resources delivered to your inbox - completely FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

React Tutorials

0 of 42 completed

Your Progress0%

Topics

Getting Started

  • What is React?
  • React vs Vanilla JavaScript
  • Setting Up Your Environment
  • Your First React App

JSX Fundamentals

  • Introduction to JSX
  • JSX Expressions and Variables
  • Conditional Rendering
  • Lists and Keys
  • Styling in JSX

Components Basics

  • Function Components
  • Props
  • Children Prop
  • Props Destructuring
  • Component Composition

State Management

  • Introduction to State
  • useState Hook
  • State Updates and Re-renders
  • Multiple State Variables
  • State Best Practices

Events and Interactivity

  • Handling Events
  • Event Objects
  • Forms in React
  • Form Validation

Side Effects and Data

  • Introduction to useEffect
  • Data Fetching
  • Cleanup Functions
  • Dependency Arrays

Advanced Hooks

  • useRef Hook
  • useContext Hook
  • useMemo Hook
  • useCallback Hook

React Patterns

  • Custom Hooks
  • Higher-Order Components
  • Render Props Pattern

React Router

  • Introduction to React Router
  • Routes and Navigation
  • Dynamic Routes and Parameters

Best Practices

  • Component Organization
  • Performance Optimization
  • Error Handling
  • Testing React Components

Real World Project

  • Building a Complete App
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo