Nasir
Nasir

Reputation: 275

Multiple input onChange issue

My component like this:

import React, { useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
import './SizeAndColor.css';
const initialData = {
  sizes: ['S', 'M', 'L', 'XL'],
  data: [
    {
      rowId: uuidv4(),
      color: 'White',
      sizes: [
        { fieldId: uuidv4(), sizeName: 'S', qty: 10 },
        { fieldId: uuidv4(), sizeName: 'M', qty: 20 },
        { fieldId: uuidv4(), sizeName: 'L', qty: 30 },
        { fieldId: uuidv4(), sizeName: 'XL', qty: 40 }
      ]
    },
    {
      rowId: uuidv4(),
      color: 'Gray',
      sizes: [
        { fieldId: uuidv4(), sizeName: 'S', qty: 50 },
        { fieldId: uuidv4(), sizeName: 'M', qty: 60 },
        { fieldId: uuidv4(), sizeName: 'L', qty: 70 },
        { fieldId: uuidv4(), sizeName: 'XL', qty: 80 }
      ]
    }
  ]
};

const initialFieldValue = {
  id: 0,
  initialData: initialData
};

export default function SizeAndColor() {
  const [state, setState] = useState(initialFieldValue);

  const onChange = (e, rowId, fieldId) => {
    const { name, value } = e.target;
    const updatedData = state.initialData.data.map(row => {
      if (rowId === row.rowId) {
        row.sizes.map(item => {
          if (fieldId === item.fieldId) {
            if (!isNaN(value)) {
              item[name] = value;
            }
          }
          return item;
        });
      }
      return row;
    });
    setState({ ...state, initialData: { ...initialData, data: updatedData } });
  };

  return (
    <div>
      <table>
        <thead>
          <tr>
            <td></td>
            {state.initialData.sizes.map(size => (
              <td key={uuidv4()}>{size}</td>
            ))}
          </tr>
        </thead>
        <tbody>
          {state.initialData.data.map(item => (
            <tr key={uuidv4()}>
              <td>{item.color}</td>
              {item.sizes.map(size => (
                <td key={uuidv4()}>
                  <input
                    type="text"
                    name="qty"
                    value={size.qty}
                    onChange={e => {
                      onChange(e, item.rowId, size.fieldId);
                    }}
                  />
                </td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

When I start typing on any input field, It work only first key press. Than again have to click the input field. Than again after first key press, can not type anything. I tried it with different scenario. If my initialData object doesn't have nested property, It works fine. how do i solve this issue, please help me.

Upvotes: 0

Views: 609

Answers (3)

Drew Reese
Drew Reese

Reputation: 202605

Issue

You are generating new React keys each render.

<div>
  <table>
    <thead>
      <tr>
        <td></td>
        {state.initialData.sizes.map(size => (
          <td key={uuidv4()}>{size}</td> // <-- here
        ))}
      </tr>
    </thead>
    <tbody>
      {state.initialData.data.map(item => (
        <tr key={uuidv4()}> // <-- here
          <td>{item.color}</td>
          {item.sizes.map(size => (
            <td key={uuidv4()}> // <-- here
              <input
                type="text"
                name="qty"
                value={size.qty}
                onChange={e => {
                  onChange(e, item.rowId, size.fieldId);
                }}
              />
            </td>
          ))}
        </tr>
      ))}
    </tbody>
  </table>

When you generate a new React key each render cycle it tells React that these are new components, so the previous ones are unmounted and a new component is instantiated. This is why the inputs lose focus.

Solution

Use the ids generated in your data so the React keys are stable, i.e. they don't change from render to render.

<div>
  <table>
    <thead>
      <tr>
        <td></td>
        {state.initialData.sizes.map((size, index) => (
          // use index since size array likely doesn't change
          <td key={index}>{size}</td> 
        ))}
      </tr>
    </thead>
    <tbody>
      {state.initialData.data.map(item => (
        <tr key={item.rowId}> // <-- use row id
          <td>{item.color}</td>
          {item.sizes.map(size => (
            <td key={size.fieldId}> // <-- use field id
              <input
                type="text"
                name="qty"
                value={size.qty}
                onChange={e => {
                  onChange(e, item.rowId, size.fieldId);
                }}
              />
            </td>
          ))}
        </tr>
      ))}
    </tbody>
  </table>

Upvotes: 1

Devolux
Devolux

Reputation: 164

Try to remove the returns, the setState should do the work.

Upvotes: 0

Nadia Chibrikova
Nadia Chibrikova

Reputation: 5036

The reason for that is that you generate random keys for input on each render, so React thinks it's a new unrelated field that doesn't need to be focused, you need to replace key={uuidv4()} with key={item.rowId} and key={size.fieldId}

Upvotes: 1

Related Questions