Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Input text losing focus upon onChange

I have a simple table of ingredients where by default three rows are generated and maintained by React useState [ingredients,setIngredients]. Typing / changing the text with trigger onChange function that in turn run setIngredients.

When I type in the textbox, it loses focus and caret disappears. I have added a unique key to each input and yet it still loses focus. If I inspect and look at the elements, it seems that the entire <tbody> rerenders.

import React, { Fragment, useState } from 'react';


const IngredientsTable = () => {
  const [rowsGenerated, setRowsGenerated] = useState(3);

  const [ingredients, setIngredients] = useState({
    1: { id: '1', name: '', proportion: '' },
    2: { id: '2', name: '', proportion: '' },
    3: { id: '3', name: '', proportion: '' },
  });

 
  const onFieldChange = ({ value, id, field }) => {
    console.log('onFieldChange triggered', { id, value, field });
    setIngredients((prevState) => ({
      ...prevState,
      [id]: { ...prevState[id], [field]: value },
    }));
  };

  const Row = ({ rowId, name, proportion }) => (
    <Fragment>
      <tr>
        <td>
          <input
            id={rowId + 'name'}
            key={rowId + 'name'}
            type="text"
            value={name}
            onChange={(e) =>
              onFieldChange({
                id: rowId,
                field: 'name',
                value: e.target.value,
              })
            }
          />
        </td>
        <td>
          <input
            id={rowId + 'proportion'}
            key={rowId + 'proportion'}
            type="text"
            value={proportion}
            onChange={(e) =>
              onFieldChange({
                id: rowId,
                field: 'proportion',
                value: e.target.value,
              })
            }
          />
        </td>
        <td>
          <button type="button">-</button>
        </td>
      </tr>
    </Fragment>
  );

  const addRow = () => {
    setRowsGenerated((prevCount) => prevCount + 1);
    setIngredients((prevState) => ({
      ...prevState,
      [rowsGenerated + 1]: { id: rowsGenerated + 1, name: '', proportion: '' },
    }));
  };

  return (
    <div className="ingredient_table">
      <table>
        <thead>
          <tr>
            <th style={{ width: '62%' }}>Name </th>
            <th style={{ width: '30%' }}>Proportion %</th>
            <th style={{ width: '8%' }}></th>
          </tr>
        </thead>
        <tbody>
          {Object.keys(ingredients).map((id, i) => {
            const rowId = id;
            return (
              <Row
                key={`ingredient${rowId}`}
                rowId={rowId}
                name={ingredients[rowId].name}
                proportion={ingredients[rowId].proportion}
              />
            );
          })}
        </tbody>
      </table>
      <button className="ingredient_add_row_button" onClick={addRow}>
        Add row
      </button>
    </div>
  );
};

export default IngredientsTable;

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

You are creating a <Row> component inside the <IngredientsTable> component, this causes issues, move the <Row> outside the other component and your issue is fixed

const { useState, useEffect } = React;

const Row = ({ rowId, name, proportion, onFieldChange }) => (
    <React.Fragment>
      <tr>
        <td>
          <input
            id={rowId + 'name'}
            key={rowId + 'name'}
            type="text"
            value={name}
            onChange={(e) =>
              onFieldChange({
                id: rowId,
                field: 'name',
                value: e.target.value,
              })
            }
          />
        </td>
        <td>
          <input
            id={rowId + 'proportion'}
            key={rowId + 'proportion'}
            type="text"
            value={proportion}
            onChange={(e) =>
              onFieldChange({
                id: rowId,
                field: 'proportion',
                value: e.target.value,
              })
            }
          />
        </td>
        <td>
          <button type="button">-</button>
        </td>
      </tr>
    </React.Fragment>
  );
  
const IngredientsTable = () => {
  const [rowsGenerated, setRowsGenerated] = useState(3);

  const [ingredients, setIngredients] = useState({
    1: { id: '1', name: '', proportion: '' },
    2: { id: '2', name: '', proportion: '' },
    3: { id: '3', name: '', proportion: '' },
  });

 
  const onFieldChange = ({ value, id, field }) => {
    //console.log('onFieldChange triggered', { id, value, field });
    setIngredients((prevState) => ({
      ...prevState,
      [id]: { ...prevState[id], [field]: value },
    }));
  };


  const addRow = () => {
    setRowsGenerated((prevCount) => prevCount + 1);
    setIngredients((prevState) => ({
      ...prevState,
      [rowsGenerated + 1]: { id: rowsGenerated + 1, name: '', proportion: '' },
    }));
  };

  return (
    <div className="ingredient_table">
      <table>
        <thead>
          <tr>
            <th style={{ width: '62%' }}>Name </th>
            <th style={{ width: '30%' }}>Proportion %</th>
            <th style={{ width: '8%' }}></th>
          </tr>
        </thead>
        <tbody>
          {Object.keys(ingredients).map((id, i) => {
            const rowId = id;
            return (
              <Row
                key={`ingredient${rowId}`}
                rowId={rowId}
                name={ingredients[rowId].name}
                proportion={ingredients[rowId].proportion}
                onFieldChange={onFieldChange}
              />
            );
          })}
        </tbody>
      </table>
      <button className="ingredient_add_row_button" onClick={addRow}>
        Add row
      </button>
    </div>
  );
};

ReactDOM.render(<IngredientsTable />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading