Keep redux state on route change - reactjs

I have react-redux and react-router in my webapp and im trying to change the route while keeping the redux state. I tried all of these and they all removed the redux state:
props.history.push({ pathname: `/my-path`}); // did this using withRouter and useHistory
<Link to={'/my-path'} />cool link</Link>
What am I doing wrong and how can I keep the state? (The reason I can't keep it in localStorage is because when the user closes that page, then the data should go away)

Redux doesn't preserve the state once you refresh the website. If you want to persist data you should use localStorage, and use the event window.onunload to clean it once your browser or page is closed.
Check out this gist
Alternatively, you can pass data when you navigate programatically just like this
import { useHistory } from 'react-router-dom'
...
function myComponentA() {
const history = useHistory()
const navigate = () => {
history.push('/pageB', {
id: 7,
name: 'Dan'
color: 'Red'
})
}
return <button onClick={navigate}>Go to page B</button>
}
...
In component B, use the hook useLocation() and then access the state property, and you should see your data right there.
import { useHistory } from 'react-router-dom'
...
function myComponentB() {
const location = useLocation()
return <h1>{location.state.name}</h1>
}
...
What calls my attention is that if you are using react-router-dom, the link button should preserve the state in your redux store. The data is only cleaned once your browser reloads. Check this sample using hooks and redux-toolkit, which could be the real solution for your problem. Once you navigate to the component B the state should persist.
For more documentation see
Redux Toolkit: https://redux-toolkit.js.org/
react-router-dom: https://reactrouter.com/web/guides/quick-start

Related

Data on redux store return to initial state

For example, I store navigate on redux store when the app start:
//in App.js file
import { useNavigate } from 'react-router-dom'
const App = () => {
const navigate = useNavigate();
useEffect(() => {
dispatch(addNavigate(navigate))
}, [dispatch, navigate]);
}
And when I use useSelector to select navigate in redux store in another component, it returns undefined. How can I fix it (if I am not use useNavigate directly in this component) and why navigate object became initial state?
From the Redux Docs:
It is highly recommended that you only put plain serializable objects, arrays, and primitives into your store. It's technically possible to insert non-serializable items into the store, but doing so can break the ability to persist and rehydrate the contents of a store, as well as interfere with time-travel debugging.
If you are okay with things like persistence and time-travel debugging potentially not working as intended, then you are totally welcome to put non-serializable items into your Redux store. Ultimately, it's your application, and how you implement it is up to you. As with many other things about Redux, just be sure you understand what tradeoffs are involved.
With all that said, if you still want to add the navigate function from the useNavigate hook you would only be able to get a valid defined value for navigate if it is called in a child component of BrowserRouter from react-router-dom.
So in the top level component this wouldn't work, instead we'd need something like this for your App.js:
import { BrowserRouter } from 'react-router-dom'
import createStore from './createReduxStore'
const store = createStore()
const App = () => (
<BrowserRouter>
<Provider store={store}>
<Content />
</Provider
</BrowserRouter>
)
Then in your Content component you can do what you initially intended:
import { useNavigate } from 'react-router-dom'
const Content = () => {
const navigate = useNavigate();
useEffect(() => {
dispatch(addNavigate(navigate))
}, [navigate]);
}

During state change in React, is it possible to append values to URL

I was wondering, if, on state change, I can add something like this to URL
www.abc.com/?state=on
without page reloading.
You can use History.replaceState() in useEffect to change url when state changing
useEffect(() => {
window.history.replaceState(
history.state,
null,
`?state=${state}`
);
}, [state]);
You could use the useNavigate hook to navigate to any path you'd want to (This is available from react-router-v6 onwards, if you are using an older version, consider using useHistory). This doesn't cause any page reloads, further using useParams you could also extract the param values to make necessary computations in the target component.
import {useNavigate} from 'react-router-dom'
//OR
import {useHistory} from 'react-router-dom' //Prior to react-router-v6
export default function Component(props){
const navigate = useNavigate()
//OR
const history = useHistory()
useEffect(() => {
history.push({pathname:'/',
search:'?state=on'
})
//OR
navigate({
pathname:'/',
search:'?state=on'},{replace:false}) //Set replace=true if you'd want to disable back navigation, false otherwise.
}, [state]);
//REST OF THE COMPONENT LOGIC
.
.
.
}
I suggest you check out the docs of react router for more information.
history.push({
pathname: '/',
search: '?state=on'
})
You can use history.push and don't worry about your page reload.
https://v5.reactrouter.com/web/api/history

Fetching data using React Router and useLocation

I want to trigger an api call each time a route is visited in react router. For example each time I route to /list The List component will call its API. It’s not possible to do this with useEffect when the component mounts because it’s not mounted each time the route is triggered. I am considering watching the location hook like so:
import { useLocation } from 'react-router-dom';
const MyComponent = () => {
const location = useLocation()
React.useEffect(() => {
// runs on location, i.e. route, change
console.log('handle route change here', location)
}, [location])
...
}
My issue is that if several components are watching the location then they will all have their useEffect triggered and call their APIs. I need to verify that this location is in fact the one that is currently displayed on the screen. I could use location.pathName to get the path but how do I verify this is the correct path for the component? Or am I going about this the wrong way?

How to detect route changes with react-router v4?

I need to detect if a route change has occurred so that I can change a variable to true.
I've looked through these questions:
1. https://github.com/ReactTraining/react-router/issues/3554
2. How to listen to route changes in react router v4?
3. Detect Route Change with react-router
None of them have worked for me. Is there a clear way to call a function when a route change occurs.
One way is to use the withRouter higher-order component.
Live demo (click the hyperlinks to change routes and view the results in the displayed console)
You can get access to the history object's properties and the closest 's match via the withRouter higher-order component. withRouter will pass updated match, location, and history props to the wrapped component whenever it renders.
https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/withRouter.md
import {withRouter} from 'react-router-dom';
class App extends Component {
componentDidUpdate(prevProps) {
if (this.props.location.pathname !== prevProps.location.pathname) {
console.log('Route change!');
}
}
render() {
return (
<div className="App">
...routes
</div>
);
}
}
export default withRouter(props => <App {...props}/>);
Another example that uses url params:
If you were changing profile routes from /profile/20 to /profile/32
And your route was defined as /profile/:userId
componentDidUpdate(prevProps) {
if (this.props.match.params.userId !== prevProps.match.params.userId) {
console.log('Route change!');
}
}
With React Hooks, it should be as simple as:
useEffect(() => {
const { pathname } = location;
console.log('New path:', pathname);
}, [location.pathname]);
By passing location.pathname in the second array argument, means you are saying to useEffect to only re-run if location.pathname changes.
Live example with code source: https://codesandbox.io/s/detect-route-path-changes-with-react-hooks-dt16i
React Router v5 now detects the route changes automatically thanks to hooks. Here's the example from the team behind it:
import { Switch, useLocation } from 'react-router'
function usePageViews() {
let location = useLocation()
useEffect(
() => {
ga.send(['pageview', location.pathname])
},
[location]
)
}
function App() {
usePageViews()
return <Switch>{/* your routes here */}</Switch>
}
This example sends a "page view" to Google Analytics (ga) every time the URL changes.
When component is specified as <Route>'s component property, React Router 4 (RR4) passes to it few additional properties: match, location and history.
Then u should use componentDidUpdate lifecycle method to compare location objects before and after update (remember ES object comparison rules). Since location objects are immutable, they will never match. Even if u navigate to the same location.
componentDidUpdate(newProps) {
if (this.props.location !== newProps.location) {
this.handleNavigation();
}
}
withRouter should be used when you need to access these properties within an arbitrary component that is not specified as a component property of any Route. Make sure to wrap your app in <BrowserRouter> since it provides all the necessary API, otherwise these methods will only work in components contained within <BrowserRouter>.
There are cases when user decides to reload the page via navigation buttons instead of dedicated interface in browsers. But comparisons like this:
this.props.location.pathname !== prevProps.location.pathname
will make it impossible.
How about tracking the length of the history object in your application state? The history object provided by react-router increases in length each time a new route is traversed. See image below.
ComponentDidMount and ComponentWillUnMount check:
React use Component-Based Architecture. So, why don't we obey this rule?
You can see DEMO.
Each page must be wrapped by an HOC, this will detect changing of page automatically.
Home
import React from "react";
import { NavLink } from "react-router-dom";
import withBase from "./withBase";
const Home = () => (
<div>
<p>Welcome Home!!!</p>
<NavLink to="/login">Go to login page</NavLink>
</div>
);
export default withBase(Home);
withBase HOC
import React from "react";
export default WrappedComponent =>
class extends React.Component {
componentDidMount() {
this.props.handleChangePage();
}
render() {
return <WrappedComponent />;
}
};

Automatic redirect after login with react-router

I wanted to build a Facebook login into my react/react-router/flux application.
I have a listener registered on the login event and would like to redirect the user to '/dashboard' if they are logged in. How can I do that? location.push didn't work very well, except after reloading the page completely.
React Router v3
This is what I do
var Router = require('react-router');
Router.browserHistory.push('/somepath');
React Router v4
Now we can use the <Redirect>component in React Router v4.
Rendering a <Redirect> will navigate to a new location. The new location will override the current location in the history stack, like server-side redirects.
import React, { Component } from 'react';
import { Redirect } from 'react-router';
export default class LoginComponent extends Component {
render(){
if(this.state.isLoggedIn === true){
return (<Redirect to="/your/redirect/page" />);
}else{
return (<div>Login Please</div>);
}
}
}
Documentation https://reacttraining.com/react-router/web/api/Redirect
React Router v0.13
The Router instance returned from Router.create can be passed around (or, if inside a React component, you can get it from the context object), and contains methods like transitionTo that you can use to transition to a new route.
React Router v2
Even though the question is already answered, I think it's relevant to post the solution that worked for me, since it wasn't covered in any of the solutions given here.
First, I'm using the router context on my LoginForm component
LoginForm.contextTypes = {
router: React.PropTypes.object
};
After that, I can access the router object inside my LoginForm component
handleLogin() {
this.context.router.push('/anotherroute');
}
PS: working on React-router version 2.6.0
React Router v3
Navigating Outside of Components
create your app with Router like this
// Your main file that renders a <Router>:
import { Router, browserHistory } from 'react-router'
import routes from './app/routes'
render(
<Router history={browserHistory} routes={routes} />,
mountNode
)
Somewhere like a Redux middleware or Flux action:
import { browserHistory } from 'react-router'
// Go to /some/path.
browserHistory.push('/some/path')
// Go back to previous location.
browserHistory.goBack()
react-router/tree/v3/docs
React Router v4.2.0
I am using React-16.2.0 & React-router-4.2.0
And I get solution by this code
this.props.history.push("/");
My working code:
.then(response => response.json())
.then(data => {
if(data.status == 200){
this.props.history.push("/");
console.log('Successfully Login');
}
})
I was following this document redirect-on-login-and-logout
I was also try by return <Redirect to='/' /> But unlucky, this not working for me.
React router v5 using hooks
These steps are for authorisation redirect. But can be used for login/logout redirection also.
The <Redirect/> accepts to prop as a string or an object. We can utilise the object to pass the redirection path after login/logout using hooks easily.
Get the pathname of url from where the <Redirect/> is called using
useLocation()
const {pathname} = useLocation()
In the to prop of <Redirect/> pass in the following object:
<Redirect to={{pathname:'/login',state: {referrer: pathname}}/>
In the Login component access the route state variable using useLocation() hook and use the useHistory() hook to redirect after successful login.
const history = useHistory();
const location = useLocation();
const login() => {
// After login success
const {state: {referrer}} = location;
history.push(referrer)
};
Check the official docs here
React Router v3
Navigating inside components
You should use withRouter decorator when it's necessary to redirect inside a component. The decorator uses context instead of you.
import {withRouter} from 'react-router'
fucntion Foo(props) {
props.router.push('/users/16');
}
export default withRouter(Foo);
withRouter(Component, [options])
A HoC (higher-order component) that wraps another component to enhance
its props with router props.
withRouterProps = {
...componentProps,
router,
params,
location,
routes
}
Pass in your component and it will return the
wrapped component.
You can explicit specify router as a prop to the wrapper component to
override the router object from context.
In your store:
data.router.transitionTo('user');
And router has:
"Route name="user" handler={User}"
User is route handler

Resources