0

I have a state with a object array. This object array is looped to render number of textboxes in the page. When I type in a textbox (after one key press), focus is removed from the textbox. Then I need to click on the textbox again to type..

I believe the issue is that in my reducer I create a new state(newArray) and return it. Due to the re-render of the newArray, mouse cursor focus is removed. I need to improve this code. How can I update the state and keep the focus on the textbox ? (Please note that reducer has many more fields inside of it and I don't want to use Redux)

const reducer = (state, action) => {
  switch (action.type) {
    case 'taskUpdate': {
      const index = state.tasks.findIndex(task => task.id == action.payload.id);
      const newArray = [...state.tasks];
      
      newArray[index].task = action.payload.task;
      newArray[index].startDate = action.payload.startDate;
      newArray[index].endDate = action.payload.endDate;

      return { 
        ...state, 
        tasks: newArray, 
       } 
    };
    default:
      throw new Error();
  }
};

const TenderCreate = () =>  {

  const [state, dispatch] = useReducer(reducer, {
    tasks: [{ id: 1,
       task: '', 
       errorTask:'',
       startDate: '', 
       errorStartDateTime:'',
       endDate:'',
       errorEndDateTime: ''
      },
    ]
  });

  const TaskRow = ({ data }) => (
    <Form.Group as={Row} controlId="task1"  className="mb-3" >
      <Form.Control
        type="text"
        placeholder=""
        name="task"
        className={data.errorTask ? 'is-invalid' : ''}
        value={data.task}
        onChange={e => {
          dispatch({
            type: 'taskUpdate',
            payload: { 
              id: data.id, 
              task: e.target.value, 
              startDate: data.startDate, 
              endDate: data.endDate 
            }
          });
        }}
      />
    </Form.Group>
  );

  return (
    <div>
      {state.tasks.map(task => (
        <TaskRow data={task} key={task.id} />
      ))}
    </div>
  );
}

Reproduced issue can be viewed here → https://stackblitz.com/edit/react-ts-dudppa?file=App.tsx

vighnesh153
  • 4,354
  • 2
  • 13
  • 27
Kasun
  • 196
  • 1
  • 14
  • I don't see anything related to your issue in this code. Can you add some more code? Also, can you attach a [Stackblitz](https://stackblitz.com/) or [CodeSandbox](https://codesandbox.io/) link which will help us debug the issue and playaround with the code? – vighnesh153 Nov 08 '22 at 08:47
  • @vighnesh153 sure! Let me do that – Kasun Nov 08 '22 at 08:50
  • What is `data` in the JSX ? – Titus Nov 08 '22 at 08:51
  • Also, in your reducer, you should update like this `newArray[index] = { ...newArray[index], ...action.payload}` to avoid mutating the same object. – vighnesh153 Nov 08 '22 at 08:57
  • @Titus data is the task which is passed to `TaskRow`. I have updated the code above with that. – Kasun Nov 08 '22 at 09:00
  • Still not seeing any issue. Please create a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). A reproducible Stackblitz link would also be helpful to debug. – vighnesh153 Nov 08 '22 at 09:10
  • @vighnesh153 it is reproducible here [https://stackblitz.com/edit/react-ts-dudppa?file=App.tsx](https://stackblitz.com/edit/react-ts-dudppa?file=App.tsx) – Kasun Nov 08 '22 at 09:34
  • You've defined the `TaskRow` component inside the `TenderCreate` component. That is the problem. – Titus Nov 08 '22 at 09:46
  • 1
    [This version](https://stackblitz.com/edit/react-ts-vtxyp5?file=App.tsx) doesn't have the bug but you should probably not pass `dispatch` as a property. – Titus Nov 08 '22 at 09:49
  • @Titus Yes. This seems to be working. I see you passed the dispatch. So, we shouldn't do that? – Kasun Nov 08 '22 at 10:33
  • Redux is not my forte but I think you need to use `mapDispatchToProps` instead of just passing the `dispatch` function as a property. – Titus Nov 08 '22 at 10:39
  • 1
    The real issue is that you have defined your component in another component. Because of that, whenever the state changes in the component, the previous component gets unmounted because the `TaskRow` function is re-created and then the new one is mounted which causes the input to be re-drawn and hence, lose focus. – vighnesh153 Nov 08 '22 at 10:46
  • The component-definition was the key part. You should update your question to include that the `TaskRow` is defined **inside** of another component. – vighnesh153 Nov 08 '22 at 10:51
  • Noted @vighnesh153 . Is the solution by @Titus is a viable solution? Move the `TaskRows` outside the component and pass the dispatcher as a parameter? or should I use the `mapDispatchToProps` ? How should I update the code? – Kasun Nov 08 '22 at 11:00
  • 1
    Yes. The example suggested by Titus is how I would do it, too. `mapDispatchToProps` is a redux concept. Since you are not using `redux`, it doesn't apply here. You can add some abstraction layer like instead of passing `dispatch`, pass `updateTask` and define a callback which hides the implementation, but that depends on your code style. – vighnesh153 Nov 08 '22 at 11:57

0 Answers0