Home > Mobile >  Generate input/label element in React child component conditionally but got a 'component is cha
Generate input/label element in React child component conditionally but got a 'component is cha

Time:10-11

As a React beginner, I want to build a simple to-do list app.
There is a list of some tasks, and list items are generated by list.map().
And If <label> is double-clicked, turn that label into <input type="text"> so the user can modify the task.
So I tried using some states in the Parent component and passed props to the child component like this.

export default function App() {

  let list = [
    {
      "id": "id-1"
      "title": "get some sleep"
    },
    {
      "id": "id-2"
      "title": "check the mailbox"
    },
    {
      "id": "id-3"
      "title": "ask question to stackoverflow"
    }
  ]

  const [editTargetTaskId, setEditTargetTaskID] = useState<string>(``);
  const [editMode, setEditMode] = useState<boolean>(false);

  const toggleEditMode = (editMode: boolean) =>
    editMode ? setEditMode(false) : setEditMode(true);

  // When double click the label, change states
  const triggerEdit = (e: React.MouseEvent<HTMLLabelElement>) => {
    setEditTargetTaskID(e.currentTarget.parentElement?.id || ``);
    toggleEditMode(false);
  };

  const editTask = (
    newInput: string,
    list: Task[],
    editTaskId: string,
    editMode: boolean
  ) => {
    setList(
      list.map((task: Task) => {
        if (task.id === editTaskId) {
          return { ...task, title: newInput };
        } else return task;
      })
    );
    toggleEditMode(editMode);
  };

  return (
      <ul>
        {list.map((task: Task) => (
          <li key={task.id} id={task.id}>
            <TaskListItem
              task={task}
              list={list}
              isEdit={editMode}
              triggerEdit={triggerEdit}
              editTaskId={editTargetTaskId}
              editTask={editTask}
            />
          </li>
        ))}
      </ul>
  )

And this is the child component list item.

interface ITaskListItemProps {
  task: Task;
  isEdit: boolean;
  editTaskId: string;
  list: Task[];
  triggerEdit: (e: React.MouseEvent<HTMLLabelElement>) => void;
  editTask: (
    newInput: string,
    list: Task[],
    editTaskId: string,
    editMode: boolean
  ) => void;
}

export default function TaskListItem({
  isEdit,
  editTaskId,
  task,
  list,
  triggerEdit,
  editTask,
}: ITaskListItemProps) {
  const [updateInput, setUpdateInput] = useState<string>(task.title);
  const handleUpdateInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    setUpdateInput(e.target.value);
  };

  if (isEdit && editTaskId === task.id) {
    return (
      <>
        <input type="text" value={updateInput} onChange={handleUpdateInput} />
        <button
          type="button"
          onClick={() => editTask(updateInput, list, editTaskId, isEdit)}
        >
          update
        </button>
      </>
    );
  } else {
    return (
      <>
        <input type="checkbox" />
        <label onDoubleClick={triggerEdit}>{updateInput}</label>
      </>
    );
  }
}

It seems to be working well at the first time, but I've got a warning message on the console 'A component is changing an uncontrolled input to be controlled.'.
How can I handle this warning? Should I use some of the other hooks like useEffect()?
Thanks in advance!

CodePudding user response:

If you notice the in your if-else statement, input text and input checkbox, the input text is controlled by a local state, while input checkbox is not.

This is likely the cause of the error.

  • Related