React Sharing State

As React applications become larger, components often need to work with the same information.

For example, imagine a shopping website with:

  • A search box
  • A product list
  • A filter panel
  • A shopping cart
  • A price summary

Several components may need access to related data. One component may update the data while another component needs to display the result.

This is where sharing state becomes important.

In React, state normally belongs to the component that declares it. When multiple components need to use the same piece of information, you need to decide where that state should live and how other components should access or update it.

React provides several patterns for handling shared state, including:

  • Lifting state up
  • Passing state through props
  • Passing event handlers through props
  • Calculating derived values
  • Designing state carefully
  • Preserving state between renders
  • Resetting state intentionally
  • Using keys to control component identity

Understanding these concepts helps you build React applications that are easier to understand, maintain, and extend.

What Does Sharing State Mean?

Sharing state means allowing multiple components to work with the same piece of information.

Consider a simple example.

Suppose you have two components:

function SearchBox() {
  // search state
}

function ProductList() {
  // product filtering
}

If the search box changes the search text and the product list needs that same search text to filter products, keeping separate copies of the search state can create problems.

Instead, the shared state can be moved to their nearest common parent.

The parent can then provide the state and the function for changing it to both components.

Conceptually:

            App
             |
      Search state
         /       \
        /         \
 SearchBox     ProductList

The parent owns the state, while the child components receive the information they need.

This is one of the most important patterns in React.

Lifting State Up

Lifting state up means moving state from a child component to a common parent component so that multiple components can use the same state.

Consider two components that need to know which tab is currently selected.

Without shared state, each component might maintain its own value.

That can result in inconsistent UI.

Instead, move the state into their parent.

import { useState } from "react";

function App() {
  const [activeTab, setActiveTab] = useState("home");

  return (
    <>
      <TabButtons
        activeTab={activeTab}
        setActiveTab={setActiveTab}
      />

      <TabContent activeTab={activeTab} />
    </>
  );
}

Now both components use the same source of truth.

Why Lift State Up?

Suppose two child components each maintain their own copy:

function ComponentA() {
  const [value, setValue] = useState(0);
}

function ComponentB() {
  const [value, setValue] = useState(0);
}

These are two completely separate state values.

Changing the state in ComponentA does not automatically change the state in ComponentB.

If both components need to stay synchronized, they should usually use shared state owned by a common parent.

Example: Temperature Converter

A temperature converter is a classic example.

You might have:

Temperature Converter
       |
   App state
   /       \
 Celsius   Fahrenheit

If the user changes Celsius, Fahrenheit should update.

The parent can store the Celsius value and pass the necessary information to both components.

import { useState } from "react";

function App() {
  const [celsius, setCelsius] = useState("");

  const fahrenheit =
    celsius === ""
      ? ""
      : (Number(celsius) * 9) / 5 + 32;

  return (
    <>
      <input
        value={celsius}
        onChange={(event) => setCelsius(event.target.value)}
        placeholder="Celsius"
      />

      <p>Fahrenheit: {fahrenheit}</p>
    </>
  );
}

The important idea is that there is one source of truth.

Image Placement: Lifting State Up

Place this image immediately after the explanation of lifting state up.

Create a modern 16:9 educational infographic showing three React components. Show two child components on the bottom connected to a parent component at the top. The parent should contain a small “Shared State” card and arrows should show state flowing from the parent to both children and update functions flowing back conceptually. Use a modern Web4Learners developer aesthetic with clean typography, subtle gradients, rounded cards, minimal text, and a polished modern UI. Avoid old-fashioned clipart and excessive text.

Sharing State Between Components

Once state has been lifted into a common parent, the parent can share it with child components using props.

For example:

import { useState } from "react";

function App() {
  const [count, setCount] = useState(0);

  return (
    <>
      <CounterDisplay count={count} />
      <CounterButtons setCount={setCount} />
    </>
  );
}

function CounterDisplay({ count }) {
  return <h2>Count: {count}</h2>;
}

function CounterButtons({ setCount }) {
  return (
    <button onClick={() => setCount((value) => value + 1)}>
      Increase
    </button>
  );
}

Here:

  • App owns the state.
  • CounterDisplay receives count.
  • CounterButtons receives setCount.
  • Both components work with the same state.

This is a common React pattern.

Sharing State Through Props

State itself does not automatically become global.

Instead, React commonly shares state through the component tree.

Parent
  |
  +-- state
  |
  +-- Child A receives state
  |
  +-- Child B receives state

The parent can also pass event handlers.

function App() {
  const [name, setName] = useState("");

  function handleNameChange(newName) {
    setName(newName);
  }

  return (
    <>
      <NameInput onNameChange={handleNameChange} />
      <Greeting name={name} />
    </>
  );
}

The child can call the function:

function NameInput({ onNameChange }) {
  return (
    <input
      onChange={(event) => onNameChange(event.target.value)}
    />
  );
}

This allows a child component to request a state update without directly modifying the parent’s state.

One Source of Truth

When multiple components need the same information, try to keep one authoritative copy of that information.

For example:

const [selectedProduct, setSelectedProduct] = useState(null);

Then pass it where needed.

Avoid creating unnecessary copies like:

const [selectedProduct, setSelectedProduct] = useState(null);
const [currentProduct, setCurrentProduct] = useState(null);

If both variables represent exactly the same concept, they can easily become inconsistent.

Derived State

Derived state is information that can be calculated from existing props or state.

For example:

const [price, setPrice] = useState(100);
const [quantity, setQuantity] = useState(3);

You can calculate the total:

const total = price * quantity;

There is usually no reason to create another state variable:

const [total, setTotal] = useState(300);

The total can be derived from price and quantity.

Why Avoid Unnecessary Derived State?

Suppose you store:

const [firstName, setFirstName] = useState("John");
const [lastName, setLastName] = useState("Smith");
const [fullName, setFullName] = useState("John Smith");

Now there are three pieces of state representing related information.

If firstName changes but fullName is not updated correctly, the UI can become inconsistent.

Instead:

const [firstName, setFirstName] = useState("John");
const [lastName, setLastName] = useState("Smith");

const fullName = `${firstName} ${lastName}`;

Now fullName is always calculated from the current values.

More Derived State Examples

Filtering:

const visibleProducts = products.filter(
  (product) => product.category === selectedCategory
);

Counting:

const completedCount = tasks.filter(
  (task) => task.completed
).length;

Calculating totals:

const total = cartItems.reduce(
  (sum, item) => sum + item.price * item.quantity,
  0
);

Checking conditions:

const isFormValid =
  email.includes("@") && password.length >= 8;

All of these are derived values.

Derived State vs State

A useful question is:

“Can I calculate this value from information I already have?”

If the answer is yes, you often do not need another state variable.

For example:

const [items, setItems] = useState([]);

You can derive:

const itemCount = items.length;

Rather than:

const [itemCount, setItemCount] = useState(0);

Avoiding Duplicate State

Duplicate state occurs when the same information is stored in more than one place.

For example:

const [user, setUser] = useState({
  name: "Alex",
  age: 25
});

const [userName, setUserName] = useState("Alex");

Now user.name and userName can become different.

If both are supposed to represent the user’s name, there is unnecessary duplication.

Prefer:

const [user, setUser] = useState({
  name: "Alex",
  age: 25
});

Then:

const userName = user.name;

Duplicate State Can Cause Bugs

Imagine:

const [items, setItems] = useState([]);
const [itemCount, setItemCount] = useState(0);

When adding an item, you must update both:

setItems(newItems);
setItemCount(newItems.length);

If you forget one update, the UI becomes inconsistent.

Instead:

const [items, setItems] = useState([]);

const itemCount = items.length;

Now there is only one source of truth.

Store Minimal State

A useful React design principle is:

Store the minimum information needed to represent the UI, and calculate the rest.

For example, instead of storing:

const [items, setItems] = useState([]);
const [totalItems, setTotalItems] = useState(0);
const [hasItems, setHasItems] = useState(false);

You can often use:

const [items, setItems] = useState([]);

const totalItems = items.length;
const hasItems = items.length > 0;

This reduces the number of state transitions your application has to manage.

Choosing State Structure

How you structure state can have a major effect on your application’s complexity.

There is no single state structure that works for every application.

The goal is to create state that is:

  • Easy to understand
  • Easy to update
  • Not unnecessarily duplicated
  • Closely related when appropriate
  • Independent when appropriate

Group Related State

Suppose a form has several fields:

const [form, setForm] = useState({
  name: "",
  email: "",
  phone: ""
});

This can be useful when the fields belong to one logical object.

Updating one property:

setForm((currentForm) => ({
  ...currentForm,
  email: "user@example.com"
}));

Separate Independent State

Sometimes separate state variables are clearer.

const [isMenuOpen, setIsMenuOpen] = useState(false);
const [isLoggedIn, setIsLoggedIn] = useState(false);

These values represent unrelated concepts, so keeping them separate makes sense.

Avoid Deeply Nested State When Possible

Consider:

const [user, setUser] = useState({
  profile: {
    contact: {
      address: {
        city: "Hyderabad"
      }
    }
  }
});

Updating deeply nested data can become cumbersome.

Sometimes a flatter structure is easier to work with.

const [city, setCity] = useState("Hyderabad");

However, nested objects are not inherently wrong. If the data naturally represents a nested object, you can keep that structure.

The important point is to choose a structure that makes updates understandable.

Avoid Contradictory State

Contradictory state occurs when two state values can describe conflicting situations.

For example:

const [isLoading, setIsLoading] = useState(false);
const [isLoaded, setIsLoaded] = useState(true);

These values can accidentally become inconsistent.

A better design might use a single status:

const [status, setStatus] = useState("idle");

Possible values could be:

idle
loading
success
error

Then the UI can derive what it needs:

const isLoading = status === "loading";

This makes impossible combinations easier to avoid.

Preserving State

React does not reset state every time a component function runs.

State is associated with a component’s position in the rendered tree and its identity.

Consider:

function App() {
  const [showDetails, setShowDetails] = useState(true);

  return (
    <div>
      {showDetails && <Profile />}
    </div>
  );
}

As long as the Profile component remains at the same position with the same identity, React can preserve its state across re-renders.

Re-rendering Does Not Automatically Reset State

Suppose:

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount((value) => value + 1)}>
      {count}
    </button>
  );
}

If the parent re-renders, React does not automatically reset count to 0.

The state belongs to that component instance.

Component Position Matters

Consider:

function App() {
  const [showCounter, setShowCounter] = useState(true);

  return (
    <div>
      {showCounter ? <Counter /> : <Counter />}
    </div>
  );
}

Although the condition changes, React sees a Counter in the same position with the same type, so its state can be preserved.

This is an important part of understanding React state.

When State Gets Reset

State can be reset when React considers a component to be a different component identity.

For example:

{isAdmin ? <AdminPanel /> : <UserPanel />}

When the rendered component type changes from AdminPanel to UserPanel, the previous component’s state does not transfer to the new component.

Another common way to reset state is by changing a component’s key.

<ProfileForm key={userId} userId={userId} />

When userId changes, the key changes.

React can then treat the component as a new instance and reset its state.

Resetting State

Sometimes you intentionally want to reset a component’s state.

For example, imagine a form:

function ProfileForm() {
  const [name, setName] = useState("");

  return (
    <input
      value={name}
      onChange={(event) => setName(event.target.value)}
    />
  );
}

If the form is used for different users, you may want the form state to start fresh whenever the user changes.

Resetting State Manually

One approach is to reset the individual state values:

setName("");
setEmail("");
setPhone("");

This can work for small components.

However, large forms may have many state values, making manual resets harder to maintain.

Resetting State with a Key

You can instead use a key:

<ProfileForm key={userId} userId={userId} />

For example:

function App() {
  const [userId, setUserId] = useState(1);

  return (
    <ProfileForm
      key={userId}
      userId={userId}
    />
  );
}

When userId changes, the key changes.

React treats the new keyed component as a different instance, so its local state starts again from its initial values.

This is a powerful and intentional use of keys.

State and Keys

Keys are commonly introduced when rendering lists:

{users.map((user) => (
  <UserCard key={user.id} user={user} />
))}

But keys have another important purpose.

They help React identify component instances.

Consider:

<Counter key="A" />

and:

<Counter key="B" />

Even though both render the same component type, the different keys tell React that they represent different component identities.

Example: Switching Counters

Consider:

function App() {
  const [person, setPerson] = useState("Alice");

  return (
    <>
      <button onClick={() => setPerson("Alice")}>
        Alice
      </button>

      <button onClick={() => setPerson("Bob")}>
        Bob
      </button>

      <Counter person={person} />
    </>
  );
}

The same Counter component remains in the same position.

Therefore, its state can be preserved when person changes.

If you want a separate counter for each person, use a key:

<Counter
  key={person}
  person={person}
/>

Now switching from Alice to Bob changes the key.

React treats them as separate component instances.

Keys Can Preserve or Reset State

This leads to an important idea:

The key is part of a component’s identity.

For example:

<Counter key="alice" />

and:

<Counter key="bob" />

are different identities.

If the key stays the same, React can preserve state.

If the key changes, React can reset the component’s state.

Keys Are Not Normal Props

A key is special to React.

For example:

<UserCard key={user.id} user={user} />

You cannot access key inside UserCard like this:

function UserCard({ key }) {
  // Don't do this
}

If the component needs the ID, pass it separately:

<UserCard
  key={user.id}
  userId={user.id}
/>

Then:

function UserCard({ userId }) {
  return <p>User ID: {userId}</p>;
}

The key is used by React to determine component identity.

userId is a normal prop available to your component.

Image Placement: State and Keys

Place this image after the explanation of state and keys.

Create a modern 16:9 React educational infographic showing two versions of the same Counter component. On the left, show a stable key and a counter retaining its state. On the right, show a changed key and the counter resetting. Include a subtle visual transition between “same identity” and “new identity”. Use modern Web4Learners styling, rounded UI cards, clean developer-focused typography, subtle gradients, minimal text, and a polished contemporary interface. Avoid excessive labels and outdated visual styles.

A Complete Example: Sharing State

Let’s combine several of these concepts into a small product filter.

Suppose we have a list of products:

import { useState } from "react";

const products = [
  { id: 1, name: "Laptop", category: "Electronics" },
  { id: 2, name: "Headphones", category: "Electronics" },
  { id: 3, name: "Backpack", category: "Accessories" },
  { id: 4, name: "Keyboard", category: "Electronics" }
];

function App() {
  const [category, setCategory] = useState("All");

  const visibleProducts =
    category === "All"
      ? products
      : products.filter(
          (product) => product.category === category
        );

  return (
    <>
      <CategoryFilter
        category={category}
        onCategoryChange={setCategory}
      />

      <ProductList products={visibleProducts} />
    </>
  );
}

function CategoryFilter({
  category,
  onCategoryChange
}) {
  return (
    <select
      value={category}
      onChange={(event) =>
        onCategoryChange(event.target.value)
      }
    >
      <option value="All">All</option>
      <option value="Electronics">Electronics</option>
      <option value="Accessories">Accessories</option>
    </select>
  );
}

function ProductList({ products }) {
  return (
    <ul>
      {products.map((product) => (
        <li key={product.id}>
          {product.name}
        </li>
      ))}
    </ul>
  );
}

There are several important React concepts here.

The Parent Owns the State

App owns:

const [category, setCategory] = useState("All");

The filter component does not own the category state.

The Parent Shares State Through Props

The selected category is passed to CategoryFilter:

<CategoryFilter
  category={category}
  onCategoryChange={setCategory}
/>

The Child Requests Updates

When the user changes the selection:

onCategoryChange(event.target.value);

The parent updates its state.

The Product List Receives Derived Data

The visible products are calculated:

const visibleProducts =
  category === "All"
    ? products
    : products.filter(
        (product) => product.category === category
      );

The filtered result is derived from existing data.

There is no unnecessary visibleProducts state.

Product Keys Provide Identity

Each product receives:

key={product.id}

This gives React a stable identity for each item.

Sharing State vs Duplicating State

Consider this design:

App
 |
 +-- SearchBox
 |     └── search state
 |
 +-- ProductList
       └── search state

This creates two separate search values.

A better structure is:

App
 |
 +── search state
 |
 +-- SearchBox
 |
 +-- ProductList

Now both components can use the same state.

The parent becomes the source of truth.

When Should You Lift State Up?

Lifting state is useful when:

  • Multiple components need the same data
  • Multiple components need to update the same data
  • Components need to stay synchronized
  • A parent needs to coordinate child components
  • You want a single source of truth

However, you should not automatically move every piece of state to the highest possible component.

Keep state as close as possible to the components that actually need it.

For example, if only a dropdown uses:

const [isOpen, setIsOpen] = useState(false);

there may be no reason to move it into the application root.

Avoid Lifting State Too Far

Suppose a small component has its own temporary UI state.

Moving that state through several parent components can make the application harder to understand.

For example:

App
 |
 Dashboard
 |
 Sidebar
 |
 Settings
 |
 Dropdown

If only Dropdown needs its open/closed state, keeping it inside Dropdown may be simpler.

The goal is not:

Put all state at the top.

The goal is:

Put shared state in the closest common owner that needs to coordinate it.

Common Mistakes When Sharing State

Creating Separate Copies of Shared Data

Avoid storing the same concept in multiple state variables.

Instead, keep one source of truth and derive other values when possible.

Updating State Directly

Do not mutate state objects or arrays directly.

Avoid:

user.name = "John";

Instead:

setUser((currentUser) => ({
  ...currentUser,
  name: "John"
}));

Lifting Everything to the Root

Not every state value needs to live in App.

Keep local state local when only one component needs it.

Storing Derived Values as State

Avoid:

const [items, setItems] = useState([]);
const [count, setCount] = useState(0);

when:

const count = items.length;

is enough.

Using Changing Values as Keys Unintentionally

Keys should normally be stable identifiers.

Avoid generating a new random key on every render:

key={Math.random()}

That can cause React to repeatedly treat components as new instances and lose their state.

Assuming Keys Are Only for Lists

Keys are also useful for intentionally controlling component identity and resetting state.

Best Practices for Sharing State

Keep One Source of Truth

If multiple components need the same information, prefer one authoritative state value.

Keep State Minimal

Do not store values that can easily be calculated from existing state or props.

Lift State Only When Necessary

Move state to a common parent when components genuinely need to coordinate around it.

Keep Local State Local

If only one component needs a value, local state is often the simplest choice.

Use Clear State Structures

Group related values when that improves clarity, and separate independent values when that makes updates easier.

Prefer Stable Keys

Use IDs or other stable identifiers when rendering dynamic lists.

Use Keys Intentionally

A key can control component identity and can be used intentionally to reset local state.

Pass Update Functions Through Props

When a child needs to request a change to parent-owned state, pass a callback.

For example:

<Child onChange={handleChange} />

This keeps the actual state update controlled by the component that owns the state.

Web4Learners Example

Imagine Web4Learners has a tutorial page with:

  • A category filter
  • A search field
  • A list of tutorials
  • A selected tutorial
  • A tutorial preview panel

A possible component structure could be:

App
 |
 +-- SearchAndFilter
 |
 +-- TutorialList
 |
 +-- TutorialPreview

If both TutorialList and TutorialPreview need to know which tutorial is selected, the selected tutorial can live in App.

const [selectedTutorialId, setSelectedTutorialId] =
  useState(null);

The list can receive:

<TutorialList
  selectedTutorialId={selectedTutorialId}
  onSelect={setSelectedTutorialId}
/>

The preview can receive:

<TutorialPreview
  tutorialId={selectedTutorialId}
/>

The list and preview are now synchronized through shared state.

The selected tutorial ID is the source of truth.

The tutorial object itself can be derived:

const selectedTutorial = tutorials.find(
  (tutorial) => tutorial.id === selectedTutorialId
);

This is often cleaner than storing both:

selectedTutorialId
selectedTutorial

as separate state values.

Practice Exercise

Build a small React application called Tutorial Explorer.

Your application should contain:

  • A list of tutorials
  • A category dropdown
  • A search box
  • A selected tutorial
  • A tutorial preview

Requirements

Store the search text in the parent component.

Store the selected category in the parent component.

Store the selected tutorial ID in the parent component.

Pass the required values to child components through props.

Create derived values for:

filteredTutorials

and:

selectedTutorial

Do not store those derived values as separate state.

Use stable tutorial IDs as keys.

Add a button that selects a tutorial.

Then add a feature where changing the selected tutorial resets the preview form.

You can achieve the reset by using a key:

<TutorialPreview
  key={selectedTutorialId}
  tutorial={selectedTutorial}
/>

This exercise brings together lifting state, shared state, derived state, state structure, and keys.

Sharing State Cheat Sheet

ConceptMeaning
Sharing stateAllowing multiple components to use the same information
Lifting state upMoving state to a common parent
Single source of truthKeeping one authoritative copy of shared information
Derived stateA value calculated from existing state or props
Duplicate stateStoring the same information in multiple places
State structureHow related state values are organized
Preserving stateKeeping state when a component retains its identity
Resetting stateStarting a component with fresh state
KeyA value React uses to identify component instances
Stable keyA key that remains associated with the same item
Callback propA function passed to a child so it can request an update

Key Takeaways

  • Shared state allows multiple components to work with the same information.
  • Lifting state up means moving state to the closest common parent that needs to coordinate it.
  • Parents can share state and update functions through props.
  • Keep one source of truth for information that multiple components depend on.
  • Avoid storing duplicate copies of the same information.
  • If a value can be calculated from existing state or props, it is often better as derived data rather than state.
  • Choose a state structure that makes your data easy to understand and update.
  • Keep state local when only one component needs it.
  • Do not move every piece of state to the application root.
  • React can preserve state when a component maintains the same identity.
  • Component type, position, and keys influence how React associates state with rendered components.
  • Changing a component’s key can intentionally reset its state.
  • Keys are useful beyond lists because they can control component identity.
  • key is a special React value and is not available as a normal prop.
  • Stable keys are important when rendering dynamic lists.
  • Good state design reduces unnecessary synchronization and makes React applications easier to maintain.

Leave a Comment