Input not re-rendering onChange with hooks

Viewed 1026

When typing and logging the input e.target.value, I get the default value + the last key stroke, but nothing re-renders. I guess that React doesn't recognize that the state changed, but I'm having a problem finding out the correct way to do this.

This is the code in question:

  const [text, setText] = useState(task.text);
  console.log(text);

  const handleInputChange = (e) => {
    setText(e.target.value);
  };

  const taskInput = (
    <form>
      <input type='text' value={text} onChange={handleInputChange} />
    </form>
  );

And the full file:

import React, { useContext, useState } from "react";
import { TaskContext } from "../context/TaskState";

const Task = ({ task }) => {
  const { deleteTask } = useContext(TaskContext);
  const { changeStatus } = useContext(TaskContext);

  const taskText = (
    <div
      className='task-text'
      onClick={() => changeStatus({ ...task, done: !task.done })}
      style={task.done ? { textDecoration: "line-through" } : null}
    >
      {task.text}
    </div>
  );

  const [text, setText] = useState(task.text);
  console.log(text);

  const handleInputChange = (e) => {
    setText(e.target.value);
  };

  const taskInput = (
    <form>
      <input type='text' value={text} onChange={handleInputChange} />
    </form>
  );

  const [option, setOption] = useState(taskText);

  return (
    <div className='task-container'>
      <button className='task-edit' onClick={() => setOption(taskInput)}>
        edit
      </button>
      <button className='task-delete' onClick={() => deleteTask(task.id)}>
        x
      </button>
      {option}
    </div>
  );
};

export default Task;

I'am using global state for the rest of the app and reducers.

3 Answers

I think, onChange in your input might cause this error. Try replacing this:

onChange={handleInputChange}

with this:

onChange={(e) => handleInputChange(e)}

e object might be not passed to your method.

Please try wrapping your taskInput value in useMemo with dependency text as when you store JSX as variable during re-render they are refering to the previous value as they don't know the variable they used have value changed.

 import React, { useMemo, useContext, useState } from "react";
 
 const taskInput = useMemo(() => (
    <form>
      <input type='text' value={text} onChange={handleInputChange} />
    </form>
 ), [text]);

The problem was the way I passed option inside the jsx.

I made the option state a boolean, converted taskText and taskInput to functions and passed option conditionally inside the jsx.

import React, { useContext, useState } from "react";
import { TaskContext } from "../context/TaskState";

const Task = ({ task }) => {
  const { deleteTask } = useContext(TaskContext);
  const { changeStatus } = useContext(TaskContext);

  const taskText = () => {
    return (
      <div
        className='task-text'
        onClick={() => changeStatus({ ...task, done: !task.done })}
        style={task.done ? { textDecoration: "line-through" } : null}
      >
        {task.text}
      </div>
    );
  };

  const [text, setText] = useState(task.text);
  console.log(text);

  const handleInputChange = (e) => {
    setText(e.target.value);
  };

  const taskInput = () => {
    return (
      <form>
        <input type='text' value={text} onChange={handleInputChange} />
      </form>
    );
  };

  const [option, setOption] = useState(true);

  return (
    <div className='task-container'>
      <button className='task-edit' onClick={() => setOption(!option)}>
        edit
      </button>
      <button className='task-delete' onClick={() => deleteTask(task.id)}>
        x
      </button>
      {option ? taskText() : taskInput()}
    </div>
  );
};

export default Task;
Related