Lesson 37 of 60 – Sharing State
62%

Sharing State

In React, sharing state means allowing multiple components to use and respond to the same state. When two or more components need the same changing information, the state can be moved to their common parent and then passed to them through props.

Note: A common React pattern is to keep shared state in the closest common parent of the components that need it. This is often called lifting state up.

1. What is Sharing State?

Sharing state means making one piece of state available to multiple components that need to work with the same data.

Parent
 ├── Component A
 └── Component B

If both components need the same state, the parent can own the state and pass the required values and functions to its children.

2. Why Share State?

Sharing state helps different parts of an application stay synchronized.

  • Multiple components can use the same data.
  • Components can respond to the same state changes.
  • Duplicate state can be avoided.
  • Data remains consistent across related components.
  • State management becomes easier to understand.

3. The Single Source of Truth

When multiple components need the same state, it is often better to keep one source of truth instead of maintaining separate copies.

Parent State
     |
     +---- Child A
     |
     +---- Child B

Both components can receive the current value from the same parent state.

4. Sharing State Through Props

The simplest way to share state between related components is through props.

function Parent() {

    const [name, setName] =
        useState("Rahul");

    return (
        <Child name={name} />
    );
}

The child receives the state value through a prop.

5. Sharing State and State Setter

A parent can pass both the state value and its setter function.

function Parent() {

    const [name, setName] =
        useState("Rahul");

    return (
        <Child
            name={name}
            setName={setName}
        />
    );
}

The child can then request changes through the setter.

6. Child Reading Shared State

function Child({ name }) {

    return (
        <h2>
            Student: {name}
        </h2>
    );
}

The child does not create another copy of the name. It uses the value provided by the parent.

7. Child Updating Shared State

function Child({
    name,
    setName
}) {

    return (
        <button
            onClick={() =>
                setName("Amit")
            }
        >
            Change Name
        </button>
    );
}

The child can update the parent's state by using the setter function passed through props.

8. Two Components Using the Same State

function Parent() {

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

    return (
        <div>

            <Display count={count} />

            <Counter
                setCount={setCount}
            />

        </div>
    );
}

The Display component reads the state while Counter changes it.

9. Display Component

function Display({ count }) {

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

This component only displays the shared state.

10. Counter Component

function Counter({ setCount }) {

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

The Counter component changes the state owned by its parent.

11. Sharing State Between Sibling Components

Sibling components cannot directly share state with each other. Their common parent can own the state and pass it to both.

       Parent
       /    \
      /      \
   Child A  Child B

The parent becomes the common source of the shared state.

12. Sibling Example

function Parent() {

    const [message, setMessage] =
        useState("");

    return (
        <div>

            <Input
                setMessage={setMessage}
            />

            <Display
                message={message}
            />

        </div>
    );
}

Input changes the shared state, while Display reads the same state.

13. Sharing Input State

function Input({ setMessage }) {

    return (
        <input
            onChange={e =>
                setMessage(
                    e.target.value
                )
            }
            placeholder="Type message"
        />
    );
}

14. Displaying Shared Input

function Display({ message }) {

    return (
        <h2>
            Message: {message}
        </h2>
    );
}

The Display component automatically receives the latest state through its prop when the parent re-renders.

15. Controlled Components and Shared State

Forms often use shared state to keep input values synchronized with other parts of the interface.

function Parent() {

    const [email, setEmail] =
        useState("");

    return (
        <div>

            <EmailInput
                email={email}
                setEmail={setEmail}
            />

            <p>
                Email: {email}
            </p>

        </div>
    );
}

16. Sharing Object State

A parent can share an object containing several related values.

const [student, setStudent] =
    useState({
        name: "Rahul",
        course: "React",
        age: 20
    });

Child components can receive the complete object or only the properties they need.

17. Updating Shared Object State

setStudent({
    ...student,
    course: "JavaScript"
});

The spread operator keeps the existing properties while changing the selected property.

18. Passing Only Required Data

A parent does not always need to pass the complete state object.

<StudentName
    name={student.name}
/>

<StudentCourse
    course={student.course}
/>

Passing only required values can make component interfaces clearer.

19. Sharing State with Event Handlers

A parent can provide callback functions that allow child components to request state changes.

function Parent() {

    const [selected, setSelected] =
        useState(null);

    function selectStudent(id) {
        setSelected(id);
    }

    return (
        <StudentList
            onSelect={selectStudent}
        />
    );
}

20. Callback Props

A function passed from a parent to a child is commonly called a callback prop.

<Child
    onSelect={handleSelect}
/>

The child can call the function when an event occurs.

function Child({ onSelect }) {

    return (
        <button
            onClick={() =>
                onSelect(10)
            }
        >
            Select
        </button>
    );
}

21. Sharing State in a Shopping Cart

A shopping application may have a cart state that needs to be used by several components.

function App() {

    const [cart, setCart] =
        useState([]);

    return (
        <div>

            <ProductList
                setCart={setCart}
            />

            <Cart
                cart={cart}
            />

        </div>
    );
}

ProductList can add products while Cart displays the shared cart.

22. Sharing State in a Search Interface

function App() {

    const [search, setSearch] =
        useState("");

    return (
        <div>

            <SearchBox
                search={search}
                setSearch={setSearch}
            />

            <Results
                search={search}
            />

        </div>
    );
}

Both SearchBox and Results work with the same search state.

23. Sharing State with Tabs

A tab interface can keep the selected tab in a parent component.

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

return (
    <div>

        <Tabs
            activeTab={activeTab}
            setActiveTab={setActiveTab}
        />

        <TabContent
            activeTab={activeTab}
        />

    </div>
);

24. Sharing State vs Duplicate State

Shared State Duplicate State
One source of truth. Multiple independent copies.
Components stay synchronized. Copies can become inconsistent.
Updates can be coordinated. More synchronization may be required.
Useful when components depend on the same data. Useful when values are intentionally independent.

25. Sharing State with Context

For state that needs to be accessed by many components at different levels of the component tree, Context can be useful.

const UserContext =
    createContext(null);

<UserContext.Provider
    value={user}
>

    <App />

</UserContext.Provider>

Components can then use useContext to read the shared value.

26. Sharing State with useReducer

When shared state has complex update logic, useReducer can organize state transitions.

const [
    state,
    dispatch
] = useReducer(
    reducer,
    initialState
);

The state and dispatch function can then be passed to child components or provided through Context.

27. Choosing the Right State Location

A useful approach is to keep state as close as possible to the components that need it.

  • If one component needs the state, keep it there.
  • If sibling components need it, move it to their common parent.
  • If many distant components need it, consider Context.
  • If update logic is complex, consider useReducer.

28. Common State Sharing Mistakes

  • Keeping duplicate copies of the same state unnecessarily.
  • Passing too much state to components that do not need it.
  • Mutating shared objects directly.
  • Creating unnecessary global state.
  • Using Context when simple props would be clearer.
  • Keeping shared state too deep in the component tree.
  • Forgetting to pass the state setter or callback when a child needs to update state.

29. Complete Shared State Example

import { useState } from "react";

function App() {

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

    return (
        <div>

            <Display
                count={count}
            />

            <Counter
                setCount={setCount}
            />

        </div>
    );
}

function Display({ count }) {

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

function Counter({ setCount }) {

    function increase() {

        setCount(
            previous =>
                previous + 1
        );

    }

    function decrease() {

        setCount(
            previous =>
                previous - 1
        );

    }

    return (
        <div>

            <button onClick={increase}>
                Increase
            </button>

            <button onClick={decrease}>
                Decrease
            </button>

        </div>
    );
}

export default App;

Here, the parent owns the state. Display reads the state and Counter updates the same state.

30. Sharing State Best Practices

  • Keep one source of truth for shared data.
  • Lift state to the closest common parent when necessary.
  • Pass only the data a component needs.
  • Pass callback functions when children need to request updates.
  • Do not duplicate the same state unnecessarily.
  • Use functional state updates when the new state depends on previous state.
  • Use Context for suitable shared values across distant components.
  • Use useReducer when shared state transitions become complex.
  • Avoid unnecessary global state.
  • Keep state ownership clear and predictable.

📌 Key Points

  • Sharing state allows multiple components to use the same changing data.
  • A common parent can own shared state.
  • Props can pass shared values to child components.
  • Setter functions and callback props allow children to request state changes.
  • Sibling components can share state through their common parent.
  • Keeping one source of truth helps prevent inconsistent data.
  • Context can help when many distant components need shared data.
  • useReducer can organize complex shared state updates.
  • State should not be duplicated unnecessarily.
  • Choose the simplest state-sharing approach that fits the component structure.

🧠 Quick Quiz

Question: What is a common way to share state between sibling components in React?