React Routing causes inputs to lose focus

Viewed 833

I have an app with a component that has inputs in it. When the user changes the inputs, I want to update the query string in the URL to reflect the new state. I achieve this by detecting a difference between the current location.search values and the query string that my state would generate. If there's a difference, I return a <Redirect> component with the query args reflecting the new state.

The problem is that after every such redirect, the component re-renders and my inputs are replaced by new inputs, meaning that the input I just changed no longer exists, so it doesn't have focus. If it's a text input, then it loses focus after every keystroke.

How I dealt with it was to remember the ID of the element that had focus when the state changed and then set focus to the new element based on document.getElementById once the component is done re-rendering.

I've reduced the issue down to a minimal app that is at https://codesandbox.io/s/github/TrevorMills/react-routing-conundrum. The problem manifests on both functional components and React.Component classes.

My mechanism seems hacky to me. My question to the React experts out there is, have I missed something? Is there a better way to get the state into the location query parms than <Redirect>? I tried adding key to my inputs but it didn't make a difference. If my mechanism seems legit, is there a way to apply it to the app on whole in a higher order component?

Thanks in advance.

2 Answers

Since you were using redirect, on every change of input value, the component rendering toggles between the returned value and the redirect causing all components to remount instead of re-render. This causes the input field to lose focus.

Avoid using <Redirect /> and use history.replace after updating the state.

Instead of doing history.replace in onChangeText function, you can write a useEffect, like this.

useEffect(() => {
    props.history.replace(props.location.pathname + "?" + qs.stringify(state));
  }, [state]);

Updated working example is here:

https://codesandbox.io/s/cocky-swirles-kcqwo?file=/src/App.js

I've edited your functional component version of the code, however the changes I made should be applicable to both.

The main thing I did is I removed the state duplication. Instead of duplicating the state from the route to the component, I just made the component use the query params directly as state. And then I made the updateState function update the query params using props.history.replace which is how React Router allows imperative navigation.

Hope this helps!

https://codesandbox.io/s/great-williamson-0pxyb

Related