Home > Back-end >  state change not triggering a re-render
state change not triggering a re-render

Time:10-10

I have a component call ExpenseList.js which does look like below. But my problem is when I tried to edit item and click save, setting isEditable inside "Save" button event handler does not trigger re-render.

import { useState } from "react";
import { useExpenses, useExpenseDispatch } from "./BudgetContext.js";

export default function ExpenseList() {
  const expenses = useExpenses();
  return (
    <ul>
      {expenses.map((expense) => (
        <li key={expense.id}>
          <Expense expense={expense} />
        </li>
      ))}
    </ul>
  );
}

function Expense({ expense }) {
  const [isEditing, setIsEditing] = useState(false);
  const dispatch = useExpenseDispatch();
  let content;

  if (isEditing) {
    content = (
      <>
        <input
          value={expense.description}
          onChange={(e) => {
            dispatch({
              type: "changed",
              expense: {
                ...expense,
                description: e.target.value
              }
            });
          }}
        />
        <input
          value={expense.amount}
          onChange={(e) => {
            dispatch({
              type: "changed",
              expense: {
                ...expense,
                amount: e.target.value
              }
            });
          }}
        />
        <button onClick={() => setIsEditing(false)}>Save</button>
      </>
    );
  } else
    content = (
      <>
        <span>{expense.description}</span> : <span>{expense.amount}</span>
        <button onClick={() => setIsEditing(true)}>Edit</button>
      </>
    );

  return (
    <label>
      {content}
      <button
        onClick={() => {
          dispatch({
            type: "deleted",
            id: expense.id
          });
        }}
      >
        Delete
      </button>
    </label>
  );
}

I was dabbling with this for hours, I think extra pair of eyes could point out what is going wrong?

Sandbox: https://codesandbox.io/s/clever-keller-l5z42e?file=/ExpenseList.js:0-1614

CodePudding user response:

Documentation reference

Content model: Phrasing content, but with no descendant labelable elements unless it is the element's labeled control, and no descendant label elements.

As mentioned above, label has 2 different labelable elements unless it is the element's labeled control. When you are in edit mode, you have 3 different labelable elements (input-description, input-amount and button-save) which causes problems with event propagation.

But when you are not in edit mode, it just has 1 labelable element which is the edit button and hence, it works.

For solving your issue, you can swap the label at the root with something like div and then use labels explicitly for each of the inputs in content.

function Expense({ expense }) {
  const [isEditing, setIsEditing] = useState(false);  
  let content;

  if (isEditing) {
    content = (
      <>
        <label>
          Description: 
          <input
            value={expense.description}
            onChange={...}
          />
        </label>
        <label>
          Amount: 
          <input
            value={expense.amount}
            onChange={...}
          />
        </label>
        <button onClick={() => setIsEditing(false)}>Save</button>
      </>
    );
  } else
    content = (
      <>
        <button onClick={() => setIsEditing(true)}>Edit</button>
      </>
    );

  return (
    <div>
      {content}
      <button>Delete</button>
    </div>
  );
}
  • Related