Programmer
Programmer

Reputation: 79

I know that using props in the initial state is an antipattern in React. How can I achieve the same result without using the prop in the state?

Todo app In my Todo app, I have a Todos List data, and the user can edit todo titles and save or cancel. Saving will change the data in the state, and in case of canceling, the title will remain the same. That's why I decided to have another state in my Task component: const [editedTitle, setEditedTitle] = useState(title); for saving the value during input onChange, and initializing the value from the prop. I know that using props in the initial state is an antipattern in React. How can I achieve the same result without using the prop in the state?

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>

function Task({ id, title, done, onSaveTodo, onDeleteTodo, onDoneTodo }) {
  const [isEditing, setIsEditing] = useState(false);
  const [editedTitle, setEditedTitle] = useState(title);

  let todoContent;
  if (isEditing) {
    todoContent = (
      <>
        <input
          value={editedTitle}
          defaultValue={title}
          onChange={(e) => setEditedTitle(e.target.value)}
        />
        <button
          onClick={() => {
            setIsEditing(false);
            setEditedTitle(title);
          }}
        >
          Cancel
        </button>
        <button
          onClick={() => {
            setIsEditing(false);
            onSaveTodo(id, editedTitle);
          }}
        >
          Save
        </button>
      </>
    );
  } else {
    todoContent = (
      <>
        <label>{title}</label>{" "}
        <button onClick={() => setIsEditing((prevState) => !prevState)}>
          Edit
        </button>
      </>
    );
  }

  return (
    <li>
      <input
        type="checkbox"
        checked={done}
        onChange={(e) => onDoneTodo(id, e.target.checked)}
      />{" "}
      {todoContent}
      <button onClick={() => onDeleteTodo(id)}>Delete</button>
    </li>
  );
}
 

Upvotes: 0

Views: 75

Answers (1)

Adam Jenkins
Adam Jenkins

Reputation: 55792

Regarding "don't put props in state" - it's a general rule, emphasis on general. When you are "editing" a prop (and using controlled state in your component) you have no choice but to put props in state like you have.

This is how to use a controlled component:

It's inappropriate to use defaultValue at the same time as you use value and onChange. These are mutually exclusive. defaultValue is for non-controlled inputs while value and onChange are for controlled inputs.

Get rid of defaultValue and do this:

I believe the change you need to do is simple.

It should just be to change this:

        <input
          value={editedTitle}
          defaultValue={title}
          onChange={(e) => setEditedTitle(e.target.value)}
        />

to this:

        <input
          value={editedTitle}
          onChange={(e) => setEditedTitle(e.target.value)}
        />

The alternative is to use an uncontrolled component (just defaultValue, no value/onChange) and use a ref (and no editedTitle state):

const inputRef = useRef();

<input defaultValue={title} ref={inputRef}/>

and then onSave you'd do:

saveToDo(id,inputRef.current.value)

Upvotes: 1

Related Questions