0

The React application I'm building has a TaskPanel component that uses a list of task names from its state to render a list of divs with Task components. The TaskPanel component uses this function in render() to make that happen:

showTasks() {
    let taskList = [];
    console.log(`in show tasks: ${Object.keys(this.state.tasks)}`);
    console.log(`selected in show tasks: ${this.state.selected}`);
    Object.keys(this.state.tasks).forEach((task_name, index) => {
      taskList.push(
        <div className="task_row_container">
          <TaskCheck
            task_name={task_name}
            isSelected={this.state.selected.includes(task_name)}
            handler={this.handleCheck}
          />
          <Task
            name={task_name}
            data={this.state.tasks[task_name]}
            key={index}
          />
          <ActionTab
            name={task_name}
            data={this.state.tasks[task_name]}
            key={task_name}
          />
        </div>
      );
    });
    return taskList;
  }

Selecting the task to be deleted: enter image description here

After pressing the delete button: enter image description here

The problem arises when I select a task using one of the checkboxes and use the delete button. It is always the task that is last in the list that is visually removed. From debugging I've discovered that my checkboxes are working as intended. Inside my delete function I can see that it gets the proper name of the selected task to be deleted. It deletes the task just fine from the backend and the state seems to also update properly to reflect the task being deleted. Even in my showTasks function I can see that it is iterating through the correct remaining tasks.

This is how my delete function works:

deleteTask() {
    var temp_tasks = this.state.tasks;
    this.state.selected.forEach((task_name) => {
      if (task_name in temp_tasks) {
        delete temp_tasks[task_name];
      }
    });
    this.setState({
      ...this.state,
      selected: [],
      allSelected: false,
      tasks: temp_tasks,
    });
    console.log(this.state.tasks);
    ipcRenderer.send("REACT_MODIFY_DATA", {
      file: "tasks",
      data: JSON.stringify(temp_tasks),
    });
  }

The frontend only updates to show the correct remaining tasks once I either fully refresh the browser, or navigate away and back to the TaskPanel. I also noticed from debugging that render() and showTasks seems to be called twice when I delete, and I have a feeling this could be related to my issue. I am at a loss for how to fix this... Any help is appreciated!

Please let me know if there's any other info I can provide to help!

[EDIT] This is my render function. It's a bit convoluted at the moment, but looking to minimize some of these event handlers:

render() {
    return (
      <div className="task_panel">
        <div className="task_header">
          <div className="task_header_title">Tasks</div>
          <div className="task_button_grid">
            <button
              className="start_button"
              onClick={(e) => this.startTasks(["test"])}
            >
              <div className="button_icon_container">
                <svg
                  height="2vh"
                  className="button_icon"
                  viewBox="0 0 512 512"
                  xmlns="http://www.w3.org/2000/svg"
                >
                  <path d="m405.28 201.19l-274.48-187.91c-12.676-8.684-25.448-13.28-36.064-13.28-20.524 0-33.22 16.472-33.22 44.044v406.12c0 27.54 12.68 43.98 33.156 43.98 10.632 0 23.2-4.6 35.904-13.308l274.61-187.9c17.66-12.104 27.44-28.392 27.44-45.884 4e-3 -17.48-9.664-33.764-27.344-45.864z" />
                </svg>
              </div>
              Start
            </button>
            <button
              className="stop_button"
              onClick={(e) => console.log("Clicked Stop Button.")}
            >
              <div className="button_icon_container">
                <svg
                  height="2.25vh"
                  className="button_icon"
                  viewBox="0 0 24 24"
                  xmlns="http://www.w3.org/2000/svg"
                >
                  <path d="M11.9999 0.333313C5.54825 0.333313 0.333252 5.54831 0.333252 12C0.333252 18.4516 5.54825 23.6666 11.9999 23.6666C18.4516 23.6666 23.6666 18.4516 23.6666 12C23.6666 5.54831 18.4516 0.333313 11.9999 0.333313ZM17.8333 16.1883L16.1883 17.8333L11.9999 13.645L7.81159 17.8333L6.16659 16.1883L10.3549 12L6.16659 7.81165L7.81159 6.16665L11.9999 10.355L16.1883 6.16665L17.8333 7.81165L13.6449 12L17.8333 16.1883Z" />
                </svg>
              </div>
              Stop
            </button>
            <button
              className="add_button"
              onClick={(e) => {
                this.showModal(e);
              }}
            >
              <div className="button_icon_container">
                <svg
                  height="2.25vh"
                  className="button_icon"
                  viewBox="0 0 18 18"
                  xmlns="http://www.w3.org/2000/svg"
                >
                  <path d="M18 10.2857H10.2857V18H7.71429V10.2857H0V7.71429H7.71429V0H10.2857V7.71429H18V10.2857Z" />
                </svg>
              </div>
              Add
            </button>
            <button
              className="edit_button"
              onClick={(e) => console.log(this.state.selected)}
            >
              <div className="button_icon_container">
                <svg
                  height="2.25vh"
                  className="button_icon"
                  viewBox="0 0 20 20"
                  xmlns="http://www.w3.org/2000/svg"
                >
                  <path d="M0 15.0422V19H3.95778L15.6306 7.32718L11.6728 3.36939L0 15.0422ZM18.6913 4.26649C19.1029 3.85488 19.1029 3.18997 18.6913 2.77836L16.2216 0.308707C15.81 -0.102902 15.1451 -0.102902 14.7335 0.308707L12.8021 2.24011L16.7599 6.19789L18.6913 4.26649Z" />
                </svg>
              </div>
              Edit
            </button>
            <button
              className="delete_button"
              onClick={(e) => this.deleteTask()}
            >
              <div className="button_icon_container">
                <svg
                  height="2.25vh"
                  className="button_icon"
                  viewBox="0 0 16 20"
                  xmlns="http://www.w3.org/2000/svg"
                >
                  <path d="M1.11111 17.7778C1.11111 19 2.11111 20 3.33333 20H12.2222C13.4444 20 14.4444 19 14.4444 17.7778V4.44444H1.11111V17.7778ZM15.5556 1.11111H11.6667L10.5556 0H5L3.88889 1.11111H0V3.33333H15.5556V1.11111Z" />
                </svg>
              </div>
              Delete
            </button>
          </div>
        </div>
        <div className="task_container">
          <div className="task_labels_header_container">
            <div className="task_labels_header">
              <div className="task_labels">
                <input
                  type="checkbox"
                  onChange={(e) => this.handleCheckAll(e)}
                  checked={this.state.allSelected}
                />
              </div>
              <div className="task_labels">Name</div>
              <div className="task_labels">Wallet</div>
              <div className="task_labels">Contract Name</div>
              <div className="task_labels">Mint Price</div>
              <div className="task_labels">Max Gas Fee</div>
              <div className="task_labels">Priority Fee</div>
              <div className="task_labels">Start Time</div>
            </div>
            <div className="action_header_container">
              <div className="action_header">Action</div>
            </div>
          </div>
          <div className="task_list_container">{this.showTasks()}</div>
          {this.state.modalVisible === true && (
            <div className="add_modal_container">
              <div className="add_modal">
                <div className="add_modal_title">Add Task</div>
                <div className="form_container">
                  <form className="add_modal_form">
                    <input
                      className="add_modal_text"
                      placeholder="Task Name"
                      type="text"
                      value={this.state.formTaskName}
                      onChange={this.handleTaskNameChange}
                    />
                    <input
                      className="add_modal_text"
                      placeholder="Wallet Name"
                      type="text"
                      value={this.state.formWalletName}
                      onChange={this.handleWalletNameChange}
                    />
                    <input
                      className="add_modal_text"
                      placeholder="Contract Name"
                      type="text"
                      value={this.state.formContractName}
                      onChange={this.handleContractNameChange}
                    />
                    <input
                      className="add_modal_text"
                      placeholder="Mint Price"
                      type="text"
                      value={this.state.formTxnCost}
                      onChange={this.handleTxnCostChange}
                    />
                  </form>
                  <form className="add_modal_form">
                    <input
                      className="add_modal_text"
                      placeholder="Max Gas Fee"
                      type="text"
                      value={this.state.formMaxFee}
                      onChange={this.handleMaxFeeChange}
                    />
                    <input
                      className="add_modal_text"
                      placeholder="Max Priority Fee"
                      type="text"
                      value={this.state.formPriorityFee}
                      onChange={this.handlePriorityFeeChange}
                    />
                    <input
                      className="add_modal_text"
                      placeholder="Mint Parameters"
                      type="text"
                      value={this.state.formMintParams}
                      onChange={this.handleMintParamsChange}
                    />
                    <input
                      className="add_modal_text"
                      placeholder="Send on Pending Time"
                      type="text"
                      value={this.state.formSendPending}
                      onChange={this.handleSendPendingChange}
                    />
                  </form>
                </div>
                <div className="add_modal_button_container">
                  <button
                    className="add_modal_cancel"
                    type="button"
                    onClick={(e) => this.hideModal(e)}
                  >
                    Cancel
                  </button>
                  <button
                    className="add_modal_finish"
                    type="button"
                    onClick={this.handleSubmit}
                  >
                    Add Task
                  </button>
                </div>
              </div>
            </div>
          )}
        </div>
      </div>
    );
  }
}
5
  • Not confident this is the source of your bug, but you really shouldn't mutate objects storing state, as it can lead to unpredictable behavior. Instead of that delete in the loop, use a filter to create a copy of the array without the deleted items. Commented Mar 7, 2022 at 7:14
  • show render function Commented Mar 7, 2022 at 13:25
  • @NickBailey Didn't realize that could cause problems! Thanks for the info I'll try that. What is the best practice for mutating dictionary state objects? If I want to add a key-value pair would I do this.state.dict[key] = value or is it better to have a temp dictionary and use this.setState to update state? Commented Mar 7, 2022 at 21:05
  • @SomeoneSpecial Just added it now. It's a bit much at the moment, looking to minimize some of the event handlers Commented Mar 7, 2022 at 21:06
  • Yep, React state is explicitly supposed to be immutable. Commented Mar 7, 2022 at 21:09

1 Answer 1

1

I do not know why you use .push to render, but we usually do the following, and add a key component to help React uniquely identify the DOM. This could be the issue.

Other than that, we will need to see your handleCheck and TaskCheck function to see how you handle your check. Are you sure your this.state.selected contains the correct value?

showTasks() {
  return Object.keys(this.state.tasks).map((task_name, index) => {
       const data = this.state.task[task_name];

       return (
           <div key={`${task_name}-${index}`} className="task_row_container">
           <TaskCheck
            task_name={task_name}
            isSelected={this.state.selected.includes(task_name)}
            handler={this.handleCheck}
          />
          ..........
           </div>
       )
  })


}
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your help this definitely makes more sense than pushing to a list. Seems that half of my problem was due to my showTasks() function, while the other half was due to my getDerivedStateFromProps() function. I had forgotten to update the tasks dictionary in the parent so every time I was changing it in deleteTasks() it was being overwritten by the parent props. Silly error on my part.
guess your problem is solved then?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.