React Router - history being reverted after a render on a parent - reactjs

I am storing some state in a context using useState, and using a useEffect in there to update history (via react-router-dom's useHistory).
After a user clicks a Cancel button, I clear these state values, and the useEffect then runs history.replace({search: ""}), emptying the query string.
However, if anything causes a re-render following that, the query string is restored with its previous value. This is despite the state in context being nulled out.
This happens without the history.push I use to update the query string in the first place, confirmed with debuggers, so it's happening somewhere outside of my code. It seems that, somehow, the router is jumping back in history one step on the render following my emptying the query string.
I'll try to write this in steps:
user interacts with a form
useState values in a context are bound to the inputs and update accordingly
useEffect watching those values uses history.push to update the query string (say /home?field1=foo)
user hits 'Cancel', which sets all of those state values to null
useEffect watching those values uses history.replace with {search: ""} (or {}), removing the query string from the URL (path is now /home)
user causes a re-render on an ancestor, and previous query string is restored to URL - /home?field1=foo
I'm at a loss, any help is much appreciated!

I haven't drilled down to the exact reason, but the problem here was that I was using useLocation and useHistory in multiple components, rendered at the same time. When search was updated, only the instance of useHistory that I called push or replace on would have the new state. The other instance would be left with the old state, and some other logic was causing it to use that false state to update the querystring again.
tl;dr if using useLocation and useHistory in multiple components, be careful as they are not aware of one another and not necessarily up to date as one would think.

Related

useEffect dependency list or useReducer when the effect pushes to history

I have a search component that is a combination of Carbon's search bar and the Elastic search UI: according to the designs, the search appears and behaves as an overlay, allowing the user to use filters etc after clicking into the search box.
I have achieved this by practically navigating to the search page when the search opens, saving the previous page in location's state, then navigating back to that page when the search closes - using hisory.push(page, state) in both cases. I had a useEffect hook controlling these page transitions with a simple, boolean isSearchOpen state as its dependency.
This was all fine until we added eslint to our solution which has warned me that my useEffect has missing dependencies - such as history. Having read up on it I found that it's very bad practice not to include all dependencies as we could introduce some bugs due to state that is not updated. However, would that be true in this particular case? The effect doesn't care what the current state of history is, it just pushes to it according to the value of isSearchOpen, when it changes. Adding history to the dependencies causes the effect to run too often and leads to undesired behaviour.
I even tried using useReducer, but that prompts the state change as its return value while pushing to history has already caused a re-render.
What is the correct solution here? Shall we just disable the rule and have the effect depend on the search state? That seems like the correct behaviour to me.

useContext value not updating

I was trying to use useContext to change the authentication state when a user logs in and this would be received in other components to conditionally show based on whether the user has logged in.
Login Context was setup as below
I passed the provider in this way
I made a button which upon clicking sets loggedIn as true
However, when I re-route to store, the value from useContext still stays as false although I have updated it in the Login component
Upon routing back to "/login" the state from useContext still shows false rather than true
Am I missing something wrt how useContext works?
That's expected. You dispatch your action, but the resulting state will be visible only on the next render phase. Not immediately. It will not happen synchronously. If you want to inspect that value, you can:
React.useEffect(() => {
console.log('loggedIn value changed', state.loggedIn)
}, [state.loggedIn])
I managed to figure out what was happening from another post: useContext does not survive between pages
So, by using react-router-dom and Link the application now works and passes the context to different pages and components but does not survive upon refresh or on opening a new tab.
Note that if this feature is required, for example login/auth, the best way is through localStorage

React Routing causes inputs to lose focus

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.
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

React / React-Native where to store dynamic refs with hooks

My app have got a lot of dynamic inputs which you can increment, remove and edit, problem is that before kill app I have to store current form state, values cannot be cleared.
Mapping values inside func component from a redux state slows down rendering because every render, refs are created again and current ref always equals null, using useRef with createRefs inside creates new issue because newly created inputs doesn't invoke newest references and rendering last input throws error.
Right now values are stored with redux persist, refs are stored in separated reducer named formRefs which is in blacklist in redux-persist.
I don't know if this may aggravate performance of redux. everything works but redux is overloaded when I'm opening redux devtools and it's crashing. Any one got similar issue? Any better solutions?

Permanent States in React - React-Router

I have a problem: I have a NavBar that loads an AJAX which retrieves a name and another data. Well, the problem is that this data stores them in states of the declared component itself as NavBar.jsx and when I use routes (react-router) the component is mounted and dismantled losing its states, therefore, every time that change of route has That re-make the AJAX call. This same problem I have for a button 'Enter', which has status 1 and when someone is logged in takes 0 status, but every time I change the route returns to state 1 (as if it were not logged, although it is actually logged). My question is: How can I create states that are maintained despite changing routes? (I know this is not possible, with states I am referring to variables, or anything I can declare that does not change value every time you reassemble the components, in this case NavBar, when changing paths. Thank you.
This can be solved in several ways.
save the returned data in localStorage, check in localStorage before doing the ajax call, is the data exists, just use it instead.
Use redux - this moves the state to the app level instead of the component, unmounting the component will not delete the state.
do not re-render the navbar on each navigation(if it's possible in your app).
See the tutorial for nested routes for react-router ;
https://github.com/reactjs/react-router-tutorial/tree/master/lessons/04-nested-routes
this option means that route changes will only re-render the "internal page" and not constant things like the navbar, submenu, footer etc.

Resources