Unlike props, which are passed in from a parent and are read-only, state is owned and managed entirely by the component itself, created with the useState hook in modern function components, and updated exclusively through its paired setter function, never by directly reassigning the state variable. Calling the setter function doesn't just update the value, it also tells React that the component, and its children, need to re-render, so the UI stays in sync with the current state — this is the fundamental mechanism that makes a React component reactive to changing data.
1Understanding State
Unlike props, which are passed in from a parent and are read-only, state is owned and managed entirely by the component itself, created with the useState hook in modern function components, and updated exclusively through its paired setter function, never by directly reassigning the state variable. Calling the setter function doesn't just update the value, it also tells React that the component, and its children, need to re-render, so the UI stays in sync with the current state — this is the fundamental mechanism that makes a React component reactive to changing data.
Never modify a state variable directly, like pushing into a state array or assigning to a state object's property — always call the setter function with a new value or a new object/array, since React specifically relies on that setter call to know a re-render is needed.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}2Practical Example
Here is a real-world application of State showing how it is used in production React code.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const incrementTwice = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
};
return <button onClick={incrementTwice}>Count: {count}</button>;
}3Best Practices
Follow these guidelines when working with State:
1. Always update state through its setter function, never by mutating the state variable directly, since React needs the setter call to trigger a re-render
2. Keep state as minimal and localized as possible, lifting it up to a shared parent only when multiple components genuinely need access to the same value
3. Use the functional updater form, setCount(prev => prev + 1), when a new state value depends on the previous one, to avoid subtle bugs from stale values in rapid successive updates
Tip: Never modify a state variable directly, like pushing into a state array or assigning to a state object's property — always call the setter function with a new value or a new object/array, since React specifically relies on that setter call to know a re-render is needed.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}4Lifting State Up
Two sibling components can't directly share state, since each holds its own independent instance even if they call the same hook. When two components need to stay in sync, move ('lift') the state to their closest common parent, which owns it and passes both the value and a way to update it down as props to each child.
function Parent() {
const [query, setQuery] = useState('');
return (
<>
<SearchInput query={query} onChange={setQuery} />
<ResultsList query={query} />
</>
);
}