0

I'm quite new to coding and I'm currently practicing the useReducer() hook in React to manage some state in a simple todo app. I'm having trouble when trying to implement the TOGGLE_TODO action. I've done it before using arrays, but as I'll likely be working with a lot of objects, I'm trying to figure out why I can't get this right. I'd say I'm learning by failing, but all I'm learning is how to switch the computer off and walk away!

Each time I toggle, I'm passing the state with the spread operator, I've tried it throughout all of the item, I've logged out the key and action.payload to make sure I'm getting a match (it works when I do a simple alert with matching).

I'm aware that the toggle isn't a toggle yet, I was just trying to simply get complete to be true.

I've tried a multitude of things to return state, I've added return to the beginning of the statement, and I"ve encountered some weird bugs along the way. As mentioned, this is quite simple state for now, but it will be more complex in another project I'm working on, so useState get's quite messy.

Any help on what I'm doing wrong here would be highly appreciated.

const initialAppState = {
  isOpen: true,
  todos: {}
};

export const ACTIONS = {
  TOGGLE_MODAL: "toggle-modal",
  ADD_TODO: "add-todo",
  TOGGLE_TODO: "toggle-todo"
};

const reducer = (state, action) => {
  // switch statement for actions
  switch (action.type) {
    case ACTIONS.TOGGLE_MODAL:
      return { ...state, isOpen: !state.isOpen };
    case ACTIONS.ADD_TODO:
      return {
        ...state,
        todos: {
          ...state.todos,
          // Object is created with Unix code as the key
          [Date.now()]: {
            todo: action.payload.todo,
            complete: false
          }
        }
      };
    case ACTIONS.TOGGLE_TODO:
      // Comparing the key and the action payload. If they match, it should set complete to 'true'. This will be updated to a toggle when working. 
      Object.keys(state.todos).map((key) => {
        if (key === action.payload) {
          return {
            ...state,
            todos: { ...state.todos, [key]: { complete: true } }
          };
        }
        return state;
      });
    default:
      throw new Error("Nope. not working");
  }
};

In the render, I pass the key as an id so it can get returned with the payload. Here is the dispatch function from the component...

const Todo = ({ id, value, dispatch }) => {
  return (
    <div className="todo">
      <h1>{`Todo: ${value.todo}`}</h1>
      <p>Done? {`${value.complete}`}</p>
      <button
        onClick={() =>
          dispatch({
            type: ACTIONS.TOGGLE_TODO,
            payload: id
          })
        }
      >
        Mark as Done
      </button>
    </div>
  );
};

and the render is using Object.entries which all works just fine. There were times when I'd get an error, or the initial todo would disappear, so I knew that state wasn't being updated correctly.

Here is the code on CodeSandbox too. I'll update here if I get it working, but I've been stuck here a couple of days. :-(

1 Answers1

0

You were almost there, good idea to index your items with Date.now()!
Only a few issues in the TOGGLE_TODO case:

  • your reducer should always return a state, your return statement should be at the end of the case, but you put it with the map's function
  • your reducer should compute a new state, not mutate the current state. So you have to create a new todo object with the complete property.

Here is how it goes:

    case ACTIONS.TOGGLE_TODO:
      const newTodos = Object.keys(state.todos).map((key) => {
        if (key === action.payload) {
          return { ...state.todos[key], complete: true } // create a new todo item
        }
        else {
          return state.todos[key]; // keep the existing item
        }
      });
      return {...state, todos: newTodos};
Louis Coulet
  • 3,663
  • 1
  • 21
  • 39
  • Oh wow, thank you SO much!! I had gotten even closer than I thought then as I'd used the `[key]` withing the return statement, but the wrong place. I've just added `complete: !state.todos[key].complete` for the toggle and it's working now. Thanks again!! – MagicMark82 Aug 30 '20 at 09:01