Skip to content

State and Props

React’s state and props are essential for managing data and controlling the flow of information between components. This section provides an overview of these concepts and introduces best practices for managing and updating state in functional components.

What is State?

State represents a component’s dynamic data that React tracks and responds to. It’s stored locally within the component and can change over time, allowing React to re-render the component in response to these changes. State is crucial for managing data that updates over time, like user inputs, form data, or fetched API data.

Some common examples of when state is used include:

  • User Inputs: Tracking form values (like text inputs or checkboxes).
  • API Data: Storing data fetched from an API.
  • UI States: Managing component visibility, toggling modes (dark/light theme), etc.

Importance of State

  1. Enables Dynamic User Interface

    State allows React components to respond to user actions and reflect real-time data changes without requiring a page reload. This ability to update only the changed parts of the UI creates a smoother, more responsive user experience.

    Example : In a to-do list app, the state can manage the list of tasks. Adding, deleting, or completing a task only updates the affected parts of the UI instead of reloading the entire page.

  2. Maintains Data Consistency within Components

    State provides a way to store and manage data that is specific to a component but may change over time. When the state updates, React ensures that the UI stays in sync with the latest data. This data consistency is essential for maintaining the correctness of the application.

    Example : In a shopping cart, each item’s quantity and total price can be managed through state. As items are added, removed, or updated, the state ensures that the displayed information reflects the current cart contents.

  3. Simplifies Complex Component Behavior

    With state, components can handle complex behaviors by tracking various data points (e.g., input values, fetched data, UI visibility, and loading statuses). By defining and updating state variables within the component, React allows developers to control these behaviors more easily.

    Example : In a form, state can track the values of multiple fields, their validation statuses, and whether the form is submitting or not. Managing all these in one place enables the form component to handle complex scenarios like error messages, success messages, and loading spinners.

  4. Improves Performance with Efficient Re-rendering

    React uses a virtual DOM to track changes to state and props, and re-renders only the components that have updated data. By updating only the necessary parts of the UI, state changes help improve performance and reduce the computational load on the browser.

    Example : In a large list or table, state can track which items are currently visible, loaded, or selected. React will re-render only the affected items rather than the entire list, improving performance, especially for applications with complex data.

  5. Supports Real-Time Updates and Data Sync

    Modern applications often require real-time data syncing, such as live chat, notifications, or stock prices. State is crucial for displaying this live data accurately within components and keeping the UI up-to-date with each change.

    Example : In a messaging app, state can track incoming messages, online user status, and typing indicators. React automatically updates the UI to reflect these real-time changes, enhancing the user experience.

Example

import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

In this example, count is the local state in the Counter component, which updates and causes the component to re-render each time the user clicks the button.

Managing local state with useState

The useState hook is the primary tool for managing local state in React’s functional components. It takes an initial value as its argument and returns an array with the current state and a function to update that state.

Syntax

const [state, setState] = useState(initialValue);
  • state : Holds the current state value.
  • setState : Function used to update the state

Initializing State

When you initialize state in a React component, you use the useState hook to set up a default value. The initial state value can be any valid JavaScript data type, depending on what the component needs to track:

  • Primitive Types: Strings, numbers, booleans.
  • Arrays: For managing lists, collections, or multiple values.
  • Objects: For managing multiple related properties within a single state object.

Example

Install the following libraries. Assuming you have installed

Terminal window
npm install react react-dom

In this example, the ParentComponent initializes a message state. ParentComponent passes the updateMessage function as a prop to ChildComponent. When the button in ChildComponent is clicked, it triggers updateMessage, updating message in ParentComponent. This allow child components to update parent state through functions passed down as props. This pattern, known as “lifting state up,” keeps state centralized and shared across related components, making it easier to manage complex interactions in the application.

Updating State in Functional Components

Updating state correctly is critical to keeping your components in sync with the application data. When calling the state updater function, you can pass a new state value directly, or use a functional update when the new state depends on the previous state. There are various techniques and nuances to state updates that help avoid bugs and improve application performance.

Functional Updates

In cases where the new state depends on the previous state, using functional updates is recommended. This is especially important in asynchronous scenarios, where relying on the previous state directly might lead to unexpected results due to timing issues.

State Batching

React optimizes performance by batching multiple state updates that occur within the same event handler, allowing all updates to be applied together in a single render cycle. This prevents unnecessary re-renders and makes updates more efficient.

Example

In this example, a ParentComponent that tracks a count state is created. The ChildComponent has a button that, when clicked, will increment the count in the parent component.

Install the following libraries. Assuming you have installed

Terminal window
npm install react react-dom
  • ParentComponent initializes a count state and defines the incrementCount function to update it.
  • incrementCount is passed down to ChildComponent as a prop, allowing the child to modify the parent’s state.
  • When the button in ChildComponent is clicked, incrementCount is triggered, incrementing count in ParentComponent.

This example demonstrates how functional updates (using prevCount => prevCount + 1) ensure accuracy even with multiple asynchronous interactions and prop drilling, where a parent function is passed to a child to modify the parent’s state. It’s a common technique when managing shared state between components.

Passing Data with Props

Props (short for “properties”) allow React components to pass data from a parent to a child, facilitating data sharing and creating modular, reusable components. Props are read-only, meaning that data flows one-way from parent to child and cannot be modified by the child component directly.

Key points to note

  • Props are immutable within the child component, which ensures that data flows in a single direction, preventing unexpected mutations.
  • By using props, components can be configured differently based on the data received, making them more versatile and reusable.
  • Props can carry any valid JavaScript data type—strings, numbers, arrays, objects, functions, etc.

Example

Install the following libraries. Assuming you have installed

Terminal window
npm install react react-dom
  • ParentComponent passes an array of products as a prop to ProductList.
  • ProductList iterates over items and renders each product using ProductItem, passing each product object as a prop.
  • ProductItem receives each product object and displays its details. This example illustrates how props can handle complex data structures, allowing for structured, nested components.

Lifting State Up in React

When multiple components need to interact with the same piece of data, it’s often beneficial to lift the state up to a common ancestor. This approach centralizes the state in a single parent component, ensuring that data remains consistent across child components without the need for redundant state copies.

By lifting state up, you can manage data in one place and pass it down via props, creating a single source of truth for that state. This technique not only improves code maintainability but also helps avoid bugs caused by data duplication or desynchronization.

When to Use Lifting State Up

Lifting state is especially useful in scenarios where:

  • Components Need Shared Access to Data : If two or more sibling components require the same data or need to respond to changes in that data, lifting state up provides a clear, unified source of truth.

  • Multiple Components Need to Modify State : When more than one component needs to modify a piece of data, lifting state up allows all changes to happen in the parent component, which then propagates the updated data to each child.

  • Avoiding Prop Drilling : When state is lifted up, the need to pass data down through multiple intermediary components can be minimized, simplifying component structure and making the data flow more straightforward.

Example

Install the following libraries. Assuming you have installed

Terminal window
npm install react react-dom

In this example,

  • TemperatureConverter (Parent): This component holds the temperature state and handles all logic related to temperature conversion. It tracks the temperature value and its scale (Celsius or Fahrenheit).

  • TemperatureInput (Child): This component renders an input for entering the temperature in a specific scale (Celsius or Fahrenheit). It receives temperature and onTemperatureChange as props from the parent.

  • Conversion Functions: toCelsius and toFahrenheit functions help convert the temperature based on the scale chosen. These functions are part of the parent component, ensuring centralized control over conversion logic.

  • Updating State: When the user enters a value, TemperatureInput calls the appropriate handler (handleCelsiusChange or handleFahrenheitChange), updating the parent state. This update then reflects in both components, keeping them synchronized.

Props Drilling

Prop drilling in React refers to the process of passing down data (props) from a parent component to a deeply nested child component through intermediary components that do not actually use the data themselves. This can result in a cluttered and less maintainable codebase, as components that don’t need the data end up receiving and passing it along just to facilitate the communication.

Alternative : Context API

The Context API provides a way to avoid prop drilling by creating a context that allows components to access shared data without needing props. It provides a way to pass data through the component tree without having to pass props down manually at every level.

Example

Install the following libraries. Assuming you have installed

Terminal window
npm install react react-dom

In this example,

  • ThemeContext : Holds the global theme state and provides the theme and toggle function to other components.
  • ThemeProvider : Wraps the app and makes theme data available to all child components.
  • We define a context using createContext() and set up a ThemeProvider component. ThemeProvider holds the theme state and toggleTheme function, which will switch between light and dark themes. The ThemeContext.Provider wraps {children}, making theme and toggleTheme available to any component within its tree.

While the Context API is a powerful tool for managing global state in small to medium-sized React applications, state management libraries like Redux, MobX, or Zustand can offer additional advantages when dealing with complex state logic and performance-sensitive applications.