skip to Main Content

I have code that toggles the value on click from an object in an array. But the console log always shows 1 0 (doubles the values)….

enter image description here

Can please tell what the issue could be?

const { useState } = React;

const App = () => {
  const [Btns, setBtns] = useState([
    { componentId: 0 },
    { componentId: 0 }
  ]);

  return (
    <div>
      <button
        onClick={(event) => {
          setBtns((oldArr) => {
            let newArr = [...oldArr];
            newArr[0].componentId = newArr[0].componentId === 0 ? 1 : 0;
            console.log(newArr[0].componentId);
            return newArr;
          });
        }}
      >
        Button
      </button>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

2

Answers


  1. It is most-likely because you have your <App> component wrapped in <StrictMode> in your sandbox.

    File: index.js

    root.render(
      <StrictMode>
        <App />
      </StrictMode>
    );
    

    Try removing the <StrictMode> tags and try to see if that helps.

    Note: Your sandbox uses strict mode, but your provided snippet does not.


    To understand how strict mode differs from non-strict mode, see:

    Why useEffect running twice and how to handle it well in React?

    Use immutable state changes

    When updating state in React, it is best to not mutate the previous state. State should be treated as immutable. If you need to change a nested property, you should clone i.e. structuredClone the current state, before modification.

    const { StrictMode, useCallback, useState } = React;
    
    const App = () => {
      const [buttons, setButtons] = useState([
        { componentId: 0 },
        { componentId: 0 }
      ]);
      
      const onClick = useCallback((event) => {
        setButtons((oldArr) => {
          // Copy
          const newArr = structuredClone(oldArr);
          // Flip bit
          newArr[0].componentId = 1 - newArr[0].componentId;
          // Print
          console.log(newArr[0].componentId); // Will log twice (this is ok)
          // Return new state
          return newArr;
        });
      }, []);
    
      return (
        <div>
          <button onClick={onClick} >
            Button
          </button>
          <hr />
          <div>
            {buttons.map(({ componentId }, index) => (
              <div key={index}>
                Component ID: {componentId}
              </div>
            ))}
          </div>
        </div>
      );
    };
    
    ReactDOM.createRoot(
        document.getElementById("root")
    ).render(
        <StrictMode>
          <App />
        </StrictMode>
    );
    .as-console-wrapper { max-height: 6rem !important; }
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
    Login or Signup to reply.
  2. I think this is related to React’s behavior in Strict mode.

    The way you are updating the state inside the setState is called an updater function.

    In React’s Strict mode, React tends to run the updated functions twice to help detect any hidden bugs.

    This is mentioned on React’s official Documentation.

    You can Check it here.

    However, I think the code used to update the state is somehow faulty.

    You should not mutate the state directly by calling newArr[0].componentId

    Instead, you can do this:

    let newArr = [...oldArr];
        newArr[0] = { ...newArr[0], componentId: newArr[0].componentId === 0 ? 1 : 0 };
        return newArr;
    

    Check my codesanbox

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search