In one line, useState in React lets a functional component store and update state values that trigger re-renders when changed.

useState = what changes

useEffect = what happens when it change

What Is “State” in a React Component?

In simple terms, a state is the data that determines how your component behaves. So if HTML is a template of UI, then the data is the state.
When the state changes, React re-renders the component so the UI always stays in sync with that data.

Before You Begin

If you’re new to React’s useState , check out my earlier article first: Understanding useState in React

It covers the basics, what useState is how to declare and update state, and how React re-renders components when state changes.

In this article, let's go a step further.

We’ll explore advanced useState concepts :

How state initialization works only once

useState hooks into React’s internal state machine.

  1. On the first render: The default value is evaluated, and a state cell is created.

  2. On re-renders: React reuses the same cell, ignoring the initializer .

  
    function Counter() {
  const [count, setCount] = useState(() => {
    console.log("Initializer runs once");
    return 0;
  });
  console.log("Component rendered");
  return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>;
}

Output in console:
“Initializer runs once”  // only once
“Component rendered”  // on every render -> Button click
  

Why You Can’t Use useState Inside Loops or Conditions

React needs to maintain a consistent order of hooks between renders.

  
    function Example() {
  if (true) {
    const [data, setData] = useState(0); // You'll get following compile time error
  }
}
  
if UseState Error

Always call hooks at the top level of your component, not inside loops, conditions, or nested functions.

  
    function Example({ condition }) {
  const [data, setData] = useState(0);
  return condition ? <div>{data}</div> : null;
}
  

Different Ways to Initialize State

  1. Simple Value

  
    const [count, setCount] = useState(0);
  
  1. From Props

  
    function Profile({ defaultName }) {
  const [name, setName] = useState(defaultName);
}
  
  1. From Expensive Computation (with lazy initializer)

  
    const [data, setData] = useState(() => getDataFromLocalStorage());
  

Tip: Using a function delays computation until the component first mounts, avoids unnecessary recalculations on re-renders.

Using Objects in State

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

function updateName(newName) {
  setUser({ name: newName }); // ❌ This removes `age` from state
}

// Rather spread the object
setUser(prev => ({ ...prev, name: newName }));
  
  
    const [name, setName] = useState("");
const [age, setAge] = useState(0);
  

Rule of thumb

Function Calls Inside useState

  
    // Called only once
function getDefaultTheme() {
  return window.matchMedia("(prefers-color-scheme: dark)").matches
    ? "dark"
    : "light";
}

const [theme, setTheme] = useState(getDefaultTheme);
  

Here getDefaultTheme is passed by reference, not called immediately. React calls it once to compute the initial state.

Lazy Initialization

Sometimes you want to recalculate the state when certain props change, not every render.

  
    function DerivedExample({ value }) {
  const [processed, setProcessed] = useState(() => process(value));

  useEffect(() => {
    setProcessed(process(value));
  }, [value]);
}
  

Clicked Twice, Still +1?

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

  const handleClick = () => {
    setCount(count + 1);
    setCount(count + 1);
  };

  return <button onClick={handleClick}>Count: {count}</button>;
}
  

You might expect the count to go up by 2 every time you click. But it only increases by 1.

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

  const handleClick = () => {
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
  };

  return <button onClick={handleClick}>Count: {count}</button>;
}
  

Now the count increases by 2 with every click.