0

In my react.js component, input field is lose focus whenever a character is typed and need to click on the input field again to type or edit the next character. Why's this happening?

Here is my code :

// State to store DataSource
  const [dataSource, setDataSource] = useState<any>(data);

  const handleOnchange = (event: any, props: any) => {
    const newData = [...dataSource];
    const itemIndex = newData.findIndex(
      (item) => item.OrderID === props.OrderID
    );
    newData[itemIndex].Freight = event.target.value;
    setDataSource(newData);
  };

  // Custom Grid Component
  const gridTemplate = (props: any) => {
    const val = props.Freight;
    return (
      <div>
        <input value={val} onChange={(event) => handleOnchange(event, props)} />
      </div>
    );
  };

I've already tried changing onChange to onBlur, but no help!

Thanks

  • lots of previous questions covering this topic, have the checked those answers? https://stackoverflow.com/questions/22573494/react-js-input-losing-focus-when-rerendering?rq=2 https://stackoverflow.com/questions/42573017/in-react-es6-why-does-the-input-field-lose-focus-after-typing-a-character?rq=2 https://stackoverflow.com/questions/42573017/in-react-es6-why-does-the-input-field-lose-focus-after-typing-a-character – Toby Hamand May 12 '23 at 10:33
  • I tried to replicate your component, there are a couple of things I didn't get. Why are you saving an array inside an input component? Could you please provide a working example on stackblitz? – noviceGuru May 12 '23 at 10:35
  • Hi @Toby Hamand, thanks for your reply I've tried almost all of them and still struggling to find a solution. – Anandhu Remanan May 12 '23 at 10:49
  • Hi @noviceGuru, thanks for your reply, Here is the link for the working sample on stackblitz: https://stackblitz.com/edit/react-ts-ggqwch?file=App.tsx . I'm trying to update a syncfusion Grid. – Anandhu Remanan May 12 '23 at 11:13

3 Answers3

1

If I understand correctly gridTemplate is a component created inside another component.

The rule is: never ever create component inside another component if it is not necessary to do that. If it is required to do that, use useCallback. Move the inner component gridTemplate outside of your component and pass the things it require as props

On each render it gets new reference and React thinks it is new component and unmounts the previuos one and mounts the new one

Oktay Yuzcan
  • 2,097
  • 1
  • 6
  • 15
  • You are correct. In the given code, gridTemplate is defined as a function component inside the App component. As you mentioned, it is generally recommended to avoid creating components inside other components unless necessary. But, I've tried moving the component outside and still failed to resolve the bug! Thanks for answering. – Anandhu Remanan May 16 '23 at 06:17
1

If the value prop is constantly updated with each keystroke, it causes the input field to lose focus because the component is being re-rendered.

You need to separate the value of the input field from the state and only update the state when necessary.

Jodes
  • 14,118
  • 26
  • 97
  • 156
  • Re-rendered, really? That sound inefficient, isn't the value just updated and therefore it loses focus? – Randy May 12 '23 at 11:45
1

By looking at the Stackblitz you shared it seems like you're not integrating Syncfusion right, they don't allow you to have controlled inputs in their templates because of their internal rendering logic.

Instead you should setup an event listener using a reference to the grid to listen to changes (keyup) on grid elements and retrieve the changes (input value). Since the input will be uncontrolled (you're only passing a defaultValue and you don't control what its current value will be) you won't have to update its value since it will be updated already (last thing the user typed).

I adapted the following code from their docs on edition to give you an idea on how to set it up:

function App() {
    const grid = React.useRef<any>() // Keep a reference to the grid, we'll use it in the created function.
    
    const gridTemplate = (props: any) => { // Similar gridTemplate function but just sets a default value and doesn't attach a React event handler, that will be handled on grid creation. 
        return (<div>
      <input id={props.OrderID} defaultValue={props.Freight} className='custemp' type='text'/>
    </div>);
    };

    const created = (args: any) => { // This function will be run by Syncfusion on creation
        grid.current.element.addEventListener('keyup', function (e: any) {
            if (e.target.classList.contains('custemp')) { // Based on condition, you can find whether the target is an input element or not.
                let row = parentsUntil(e.target, 'e-row');
                let rowIndex = row.rowIndex; // Get the row index.
                let uid = row.getAttribute('data-uid');
                let grid = document.getElementsByClassName('e-grid')[0].ej2_instances[0];
                let rowData = grid.getRowObjectFromUID(uid).data; // Get the row data.
                rowData.Freight = e.target.value; // Update the new value for the corresponding column.
                grid.current.updateRow(rowIndex, rowData); // Update the modified value in the row data.
            }
        });
    };
    return <GridComponent dataSource={data} ref={g => gridRef.current = g} height={315} created={created}>
    <ColumnsDirective>
      <ColumnDirective field='OrderID' headerText='Order ID' width='120' textAlign="Right" isPrimaryKey={true}/>
      <ColumnDirective field='OrderDate' headerText='Order Date' width='130' textAlign="Right" format='yMd'/>
      <ColumnDirective field='ShipCountry' headerText='Ship Country' width='140'/>
      <ColumnDirective field='Freight' headerText='Receipt Amount' width='140' template={gridTemplate}/>
    </ColumnsDirective>
  </GridComponent>;
}

The code was originally written in plain Javascript.

rowinbot
  • 587
  • 4
  • 13
  • Hi Rowinbot, First of all, I want to express my gratitude for providing an answer. I really appreciate it. I have certain doubts regarding the code that you've provided. For example, why did you used `const gridRef = React.useRef()`? you commented it will be used in `created()` but you didn't.. Could you please provide more clarification or explanation about it? I've referred the official documentation as you mentioned(https://rb.gy/f5mb4) and found that the input is not working in their eg. code also. Thank you so much! – Anandhu Remanan May 16 '23 at 07:13
  • @AnandhuRemanan `gridRef` was a typo, it should've been `grid`. And `grid` is used in the `created` function :) It's basically a reference to the `GridComponent` exposed interface (if it's a class component then it will be the instance of the class, if it's a function component then it'd be the exposed object from the imperative handler) that contains the methods necessary to update the rows of the grid (see `updateRow`, `getRowObjectFromUID`) – rowinbot May 17 '23 at 09:28