skip to Main Content

I have two components in my code. In the App component, I initialize the chosenCount with zero and then I set a different value(eg: 56). When I click the set button, the Counter component is receiving the chosenCount as inititalCount. Inside the Counter component, a new state is initialized with the passed in prop initialCount as counter.But the counter value is not initializing with the passed in prop and its value is always 0. Its showing the same in the UI also.
Why is the state initializing with 0. Is this the default React behavior

These are the 2 components

const App = () => {

  const [enteredNumber, setEnteredNumber] = useState(0);
  const [chosenCount, setChosenCount] = useState(0);

  const handleChange = (event) => {
    setEnteredNumber(event.target.value);
  }

  const handleSetClick = () => {
    setChosenCount(enteredNumber);
    setEnteredNumber(0);
  }

  return (
    <>
      <main>
        <section id="configure-counter">
          <h2>Set Counter</h2>
          <input type="number" onChange={handleChange} value={enteredNumber} />
          <button onClick={handleSetClick}>Set</button>
        </section>
        <Counter setCount={setChosenCount} initialCount={chosenCount} />
      </main>
    </>
  );
}

const Counter = ({initialCount}) => {

  const [counter, setCounter] = useState(initialCount);

  
  const handleDecrement = () => {
    setCounter((prevCounter) => prevCounter - 1);
  }

  const handleIncrement = () => {
    setCounter((prevCounter) => prevCounter + 1);
  } 

  return (
    <section className="counter">
      <p>
        <button onClick={handleDecrement}>
          Decrement
        </button>
        <span className="counter-output">{counter}</span>
        <button onClick={handleIncrement}>
          Increment
        </button>
      </p>
    </section>
  );
}

I tried with setting the value as a new number, I was expecting it to reflect in the UI with the new number, but it was always showing 0

3

Answers


  1. useState value is get initialized only first time so as soon as the value is initialized it won’t initialized it again. You have to use setCounter to set new value.

    First time when the component is mounted then the initial value is 0 that you are passing it to Counter, But when you change the value of the parent component then its value won’t get updated because it is already initialized.

    // value that is passed to useState is initialState not the updatedState
    const [state, setState] = useState(initialState);
    

    Parameters initialState: The value you want the state to be initially.
    It can be a value of any type, but there is a special behavior for
    functions. This argument is ignored after the initial render.

    If you pass a function as initialState, it will be treated as an initializer function. It should be pure, should take no arguments, and
    

    should return a value of any type. React will call your initializer
    function when initializing the component, and store its return value
    as the initial state. – ReactJS

    CODESANDBOX

    You can use useEffect to update value as soon as the value changes

      useEffect(() => {
        setCounter(initialCount);
      }, [initialCount]);
    
    Login or Signup to reply.
  2. It is because the behavior of state initialization in React is asynchronous, when you first render the Counter component, the initialCount prop may not have been updated yet.

    Even though chosenCount gets updated when you click the "Set" button in the App component, the Counter component’s state does not automatically update to reflect the new value of initialCount.

    you have to use the useEffect in the Counter Component.

    const [counter, setCounter] = useState(initialCount);
    
     // here is the useEffect Code
      useEffect(() => {
        setCounter(initialCount);
      }, [initialCount]);
    
      const handleDecrement = () => {
        setCounter((prevCounter) => prevCounter - 1);
      }
    
     const handleIncrement = () => {
        setCounter((prevCounter) => prevCounter + 1);
      }
    
       return (
           // rest of the component 
        )
    }
    
    Login or Signup to reply.
  3. The value you pass to useState() is only used once when the component initially mounts to determine the initial conter state value that you destructure from it (const [counter, setCounter] = ...). To quote the docs, it:

    is ignored after the initial render

    The only way to persistently modify your state is to use the setCounter state setter function it returns, changing the value passed to useState() won’t do that.

    It is generally considered an anti-pattern to have a local version of the state which is passed as props, even with updating it with useEffect() (as this causes two renders of the child to occur, see the docs for more information). Instead, for this particular example, use the state from your parent component when you render Counter rather than recreating the sate. You can use the state setter function it gets passed from the parent to update your state as well. This is known as "lifting" your state up, which you’ve effectively already done:

    const { useState } = React;
    const App = () => {
    
      const [enteredNumber, setEnteredNumber] = useState(0);
      const [chosenCount, setChosenCount] = useState(0);
    
      const handleChange = (event) => {
        setEnteredNumber(+event.target.value); // ensure it's a number
      }
    
      const handleSetClick = () => {
        setChosenCount(enteredNumber);
        setEnteredNumber(0);
      }
    
      return (
        <React.Fragment>
          <main>
            <section id="configure-counter">
              <h2>Set Counter</h2>
              <input type="number" onChange={handleChange} value={enteredNumber} />
              <button onClick={handleSetClick}>Set</button>
            </section>
            <Counter setCount={setChosenCount} initialCount={chosenCount} />
          </main>
        </React.Fragment>
      );
    }
    
    const Counter = ({initialCount, setCount}) => {
      
      const handleDecrement = () => {
        setCount((prevCounter) => prevCounter - 1);
      }
    
      const handleIncrement = () => {
        setCount((prevCounter) => prevCounter + 1);
      } 
    
      return (
        <section className="counter">
          <p>
            <button onClick={handleDecrement}>
              Decrement
            </button>
            <span className="counter-output">{initialCount}</span>
            <button onClick={handleIncrement}>
              Increment
            </button>
          </p>
        </section>
      );
    }
    
    ReactDOM.createRoot(document.body).render(<App />);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search