How can I set focus on a dynamically added field with React

Viewed 1594

I have a basic task list app that gives users the ability to add items to the task list. When the "Add Item" button is clicked I will insert a new row to the bottom of the list. The row contains an empty text field, where the user can enter their task name. I want to set the focus on this field as soon as it's push()ed into the array. I know how to set the focus using a ref if the field already exists, but I can't seem to figure it out for a dynamically added field. How can I do this?

Here is my code:

const tasks = [array_of_task_objects];
const [state, setState] = React.useState({tasks: tasks});
const newTask = {title: ''};

const addTask = () => {
    let newTasks = [...state.tasks];
    newTasks.push(newTask);
    setState({...state, tasks: newTasks});
    // Now, set focus in the input field...(how?)
};

Elsewhere:

<button onClick={addTask}>Add Task</button>

<ul>
    {
        state.tasks.map(task => {
            return(
                <li><input value={task.title}></li>
            );
        })
    }
</ul>
2 Answers

One way to do this is to have a ref that's always referring to the last textbox and then running an effect that sets focus on that last element when tasks are updated. This is a shell of an example that should basically get you there:

export default function App() {
  const [tasks, setTasks] = useState([newTask]);
  const lastRef = useRef(null);

  useEffect(() => {
    if (lastRef.current)
      lastRef.current.focus();
  }, [tasks]);

  return (
    <div className="App">
      {tasks.map((task, i) => (
        <>
          <input key={i} ref={i === tasks.length - 1 ? lastRef : undefined} />
          <br />
        </>
      ))}
      <button
        onClick={() => {
          setTasks(tasks => [...tasks, newTask]);
        }}
      >
        Add
      </button>
    </div>
  );
}

You can make the task input focus itself when it is rendered the first time.

const Task = ({value}) => {
  const ref = useRef(null);
  useEffect(() => if (ref.current) {ref.current.focus()}, [ref.current])

  return <li><input ref={ref} value={value} /></li>
}

This will work if you are only mounting one at a time. If you have multiple inputs rendered in an initial state for example you could introduce a shouldTakeFocus prop. Then you limit the effect to only run when shouldTakeFocus is true.

Related