React toggle button only works once?

I am learning React Reducer now. I want to build a toggle button that changes a boolean completed value to its opposite each time I click the button.

What I have is an array of states, each state is an object with an id and a completed value set to be true or false. Then I loop through states, setting each state as an Item component and display it on screen.

// App.js file

import React, { useReducer } from "react";
import { AppReducer } from "./AppReducer";
import Item from "./Item";

function App() {
    const initialStates = [
            id: 1,
            completed: false,
            id: 2,
            completed: false,

    const [states, dispatch] = useReducer(AppReducer, initialStates);

    return (
            { => (
                <Item item={state} key={} dispatch={dispatch} />

export default App;

In the Item component, I display whether this item is completed or not (true or false). I set up a toggle function on the button to change the completed state of the Item.

// Item.js

import React from "react";

const Item = ({ item, dispatch }) => {
    function setButtonText(isCompleted) {
        return isCompleted ? "True" : "False";

    let text = setButtonText(item.completed);

    function toggle(id){
            type: 'toggle', 
            payload: id

        text = setButtonText(item.completed);

    return (
            <button type="button" onClick={() => toggle(}>Toggle</button>

export default Item;

Here is my reducer function. Basically what I am doing is just loop through the states array and locate the state by id, then set the completed value to its opposite one.

// AppReducer.js

export const AppReducer = (states, action) => {
  switch (action.type) {
      case "toggle": {

          const newStates = states;
          for (const state of newStates) {
              if ( === action.payload) {
                  const next = !state.completed;
                  state.completed = next;
          return [...newStates];
          return states;

So my problem is that the toggle button only works once. I checked my AppReducer function, it did change completed to its opposite value, however, every time we return [...newStates], it turned back to its previous value. I am not sure why is that. I appreciate it if you can give it a look and help me.

The code is available here.


Here is the working version forked from your codesandbox

The store value updated successfully. The problem is the way of listening the new item change. dispatch is a async event, there is no guarantee the updated item will be available right after dispatch()

So the 1st thing to do is to monitor item.completed change:

useEffect(() => {
}, [item.completed]);

The 2nd thing is text = setButtonText(item.completed);, it will not trigger re-render. Therefore, convert the text to state and set it when item.completed to allow latest value to be displayed on screen

const [text, setText] = useState(setButtonText(item.completed));

Source: stackoverflow