Use react router in chrome extention [duplicate] - javascript

I want 2 pages in my Chrome extension. For example: first(default) page with list of users and second with actions for this user.
I want to display second page by clicking on user(ClickableListItem in my case). I use React and React Router. Here the component in which I have:
class Resents extends React.Component {
constructor(props) {
super(props);
this.handleOnClick = this.handleOnClick.bind(this);
}
handleOnClick() {
console.log('navigate to next page');
const path = '/description-view';
browserHistory.push(path);
}
render() {
const ClickableListItem = clickableEnhance(ListItem);
return (
<div>
<List>
<ClickableListItem
primaryText="Donald Trump"
leftAvatar={<Avatar src="img/some-guy.jpg" />}
rightIcon={<ImageNavigateNext />}
onClick={this.handleOnClick}
/>
// some code missed for simplicity
</List>
</div>
);
}
}
I also tried to wrap ClickableListItem into Link component(from react-router) but it does nothing.
Maybe the thing is that Chrome Extensions haven`t their browserHistory... But I don`t see any errors in console...
What can I do for routing with React?

I know this post is old. Nevertheless, I'll leave my answer here just in case somebody still looking for it and want a quick answer to fix their existing router.
In my case, I get away with just switching from BrowserRouter to MemoryRouter. It works like charm without a need of additional memory package!
import { MemoryRouter as Router } from 'react-router-dom';
ReactDOM.render(
<React.StrictMode>
<Router>
<OptionsComponent />
</Router>
</React.StrictMode>,
document.querySelector('#root')
);
You can try other methods, that suits for you in the ReactRouter Documentation

While you wouldn't want to use the browser (or hash) history for your extension, you could use a memory history. A memory history replicates the browser history, but maintains its own history stack.
import { createMemoryHistory } from 'history'
const history = createMemoryHistory()
For an extension with only two pages, using React Router is overkill. It would be simpler to maintain a value in state describing which "page" to render and use a switch or if/else statements to only render the correct page component.
render() {
let page = null
switch (this.state.page) {
case 'home':
page = <Home />
break
case 'user':
page = <User />
break
}
return page
}

I solved this problem by using single routes instead of nested. The problem was in another place...
Also, I created an issue: https://github.com/ReactTraining/react-router/issues/4309

This is a very lightweight solution I just found. I just tried it - simple and performant: react-chrome-extension-router

I just had to use createMemoryHistory instead of createBrowserHistory:
import React from "react";
import ReactDOM from "react-dom";
import { Router, Switch, Route, Link } from "react-router-dom";
import { createMemoryHistory } from "history";
import Page1 from "./Page1";
import Page2 from "./Page2";
const history = createMemoryHistory();
const App: React.FC<{}> = () => {
return (
<Router history={history}>
<Switch>
<Route exact path="/">
<Page1 />
</Route>
<Route path="/page2">
<Page2 />
</Route>
</Switch>
</Router>
);
};
const root = document.createElement("div");
document.body.appendChild(root);
ReactDOM.render(<App />, root);
import React from "react";
import { useHistory } from "react-router-dom";
const Page1 = () => {
const history = useHistory();
return (
<button onClick={() => history.push("/page2")}>Navigate to Page 2</button>
);
};
export default Page1;

A modern lightweight option has presented itself with the package wouter.
You can create a custom hook to change route based on the hash.
see wouter docs.
import { useState, useEffect } from "react";
import { Router, Route } from "wouter";
// returns the current hash location in a normalized form
// (excluding the leading '#' symbol)
const currentLocation = () => {
return window.location.hash.replace(/^#/, "") || "/";
};
const navigate = (to) => (window.location.hash = to);
const useHashLocation = () => {
const [loc, setLoc] = useState(currentLocation());
useEffect(() => {
// this function is called whenever the hash changes
const handler = () => setLoc(currentLocation());
// subscribe to hash changes
window.addEventListener("hashchange", handler);
return () => window.removeEventListener("hashchange", handler);
}, []);
return [loc, navigate];
};
const App = () => (
<Router hook={useHashLocation}>
<Route path="/about" component={About} />
...
</Router>
);

Related

history parameter has been removed from router in version 6.2.1?

I'm trying to use createMemoryHistory on the router and I'm getting the typescript error :{
the code is this and the message is in the image..
import { Router } from 'react-router-dom'
import { createMemoryHistory } from 'history'
const history = createMemoryHistory()
const makeSut = () => {
const sut = render(
<Router history={history}>
<Login />
</Router>
)
}
It is no longer needed to create the history object yourself. Use the BrowserRouter component instead or the Router component
the test I'm doing is on the object's history,
I found this solution
<Router navigator={history} location={history.location}>
...
</Router>

Nesting a react router inside another component while preserving a sidebar

As an exercise, I'm making a react app (still learning React) that implements a login system with firebase. Of course, to implement such a feature, react router is necessary and I have successfully implemented it. However, once the user logs in he should be able to see a sidebar alongside other content that is changed dynamically. I now need to again use react router to change those pages when a user clicks on a specific item in the sidebar without having to render the sidebar with each component. I have read the docs for nesting routers but just cant get it to work. Any help is greatly appreciated.
Here's the code:
App.js:
import "./App.css";
import LoginForm from "./components/LoginForm";
import { AuthProvider } from "./contexts/AuthContext";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Dashboard from "./components/Dashboard";
import PrivateRoute from "./components/PrivateRoute";
function App() {
return (
<div className="App">
<Router>
<AuthProvider>
<Switch>
<PrivateRoute exact path="/" component={Dashboard} />
<Route path="/login" component={LoginForm} />
</Switch>
</AuthProvider>
</Router>
</div>
);
}
export default App;
Dashboard.js:
import React from "react";
import { useAuth } from "../contexts/AuthContext";
import { useHistory } from "react-router";
import Sidebar from "./Sidebar/Sidebar";
import { useRouteMatch } from "react-router";
const Dashboard = () => {
const { currentUser, logout } = useAuth();
const history = useHistory();
let { path, url } = useRouteMatch();
const handleLogout = async () => {
try {
await logout();
history.push("/login");
} catch (error) {
console.log(error);
}
};
if (!currentUser) return null;
return (
<div>
<Sidebar logout={handleLogout} />
</div>
);
};
export default Dashboard;
PS. I'm quite new to react and any tip/critique is welcome
You can always conditionally render the sidebar.
function Sidebar() {
const { currentUser } = useAuth()
if (!currentUser) return null
// ...
}
Within your App component, just render the Sidebar component outside of the Switch:
function App() {
return (
<div className="App">
<Router>
<AuthProvider>
<Sidebar />
<Routes />
</AuthProvider>
</Router>
</div>
);
}
function Routes() {
const { currentUser } = useAuth()
return (
<Switch>
{currentUser && <PrivateRoutes />}
<PublicRoutes />
</Switch>
)
}
Basically all you need to do is render the sidebar on all routes. If you need to render custom Sidebar content based off of routes, you can add another Switch within Sidebar. You can add as many Switch components as you want as long as they are within your Router.
Even though i understand what your trying to do, i don't think you should mind put the sidebar inside the component.
React is powerfull enough to cache a lots of stuffs and disable unnecessary renders. I think the path you should go its figure out how to use wisely useCallback useMemo, memo and make all the tricks to prevent re-renders inside the sidebar components. This way you can reuse the sidebarcomponent, or any component, without to think about location.

Problems with react router trying to redirect users

i'm working on a MERNG app, and i have a problem loging out the user.
So, i have a route, that will take you to a page where the url has /profile/userId, the userID is given by the localStorage since i'm taking that value from there, and, when i logout i remove the token from the localStorage, so, if there's no values, it should take you login page.
Now, the problem is that, when i login, and get to the /profile/userId page, and i refresh the page with f5, if i logout, it won't let me do it, the redirecting doesn't work, it only works if i don't refresh the page, and that's a big issue for my app, and it's actually weird.
Maybe it's a problem with my code, i don't know that well how to use Redirect, so if you can help me with this, you're the greatest !
So this is the code
Routes with the redirect
import React from "react";
import "./App.css";
import {
BrowserRouter as Router,
Switch,
Route,
Redirect
} from "react-router-dom";
import Auth from "./website/Auth";
import SocialMedia from "./website/SocialMedia";
import SingleUser from "./website/SingleUser";
function App() {
const logIn = JSON.parse(localStorage.getItem("token"));
return (
<Router>
<Switch>
<Route exact path="/">
{logIn ? <Redirect to={`/profile/${logIn.data.id}`} /> : <Auth />}
</Route>
<Route exact path="/socialmedia" component={SocialMedia} />
<Route exact path="/profile/:id" component={SingleUser} />
</Switch>
</Router>
);
}
export default App;
To logout
import React from "react";
import { useHistory } from "react-router-dom";
import { useDispatch } from "react-redux";
const SingleUser = () => {
const history = useHistory();
const dispatch = useDispatch();
const userData = JSON.parse(localStorage.getItem("token"));
const logout = () => {
dispatch({ type: "LOGOUT" });
if (!userData) {
history.push("/");
}
};
return <div onClick={logout}>Single User</div>;
};
export default SingleUser;
My reducer where i store the token in the localStorage and remove it with logout action
const reducer = (state = { authData: null }, action) => {
switch (action.type) {
case "LOGIN":
localStorage.setItem("token", JSON.stringify({ data: action.payload }));
return {
...state,
authData: action.payload
};
case "LOGOUT":
localStorage.removeItem("token");
return {
...state,
authData: null
};
default:
return state;
}
};
export default reducer;
This is simply because you are rendering the Auth component only in the "/" route.
So if you are on a different route, you are skipping the "/" route and the user route will be shown. There are different ways on how to handle it:
Always redirect
<Switch>
<Route exact path="/">
<Auth />
</Route>
{!logIn && <Route path="*"><Redirect to={`/profile/${logIn.data.id}`} /></Route>} // This route will not always be rendered if the user should login and is not on the "/" page
<Route exact path="/socialmedia" component={SocialMedia} />
<Route exact path="/profile/:id" component={SingleUser} />
</Switch>
Redirect manually for every route
const SingleUser = () => {
const history = useHistory();
const dispatch = useDispatch();
const userData = JSON.parse(localStorage.getItem("token"));
if(!userData) histroy.push("/")
...
Early return
function App() {
const logIn = JSON.parse(localStorage.getItem("token"));
if(!login) return <Auth />
return (
<Router>
<Switch>
<Route exact path="/">
<Redirect to={`/profile/${logIn.data.id}`} />
</Route>
<Route exact path="/socialmedia" component={SocialMedia} />
<Route exact path="/profile/:id" component={SingleUser} />
</Switch>
</Router>
);
}
Additionally, userData will always be defined on logout, since you are not accessing a new lcoalState but the current one, before it got removed. It still lives in your varibale.
if (!userData) { // Remove the if
history.push("/");
}
Another quite easy solution for you might be just doing this:
<Route exact path="/" render={() => (logIn ? <SingleUser /> : <Auth />)} />
This way on every rerender it will check if there is a user and if not it will redirect you directly to the Auth Component.
Hi mike from the past !
Your code it's okay, but, when you click logout, the variable on the App.js component is still the same, so, you won't get the results you're looking for, what you have to do, it's to reload the website, so te App.js component will upload the variable and will recognize that the value of variable has changed, so you will go to login/register again!
just change your code for this one, so far everything work as expected
import React from "react";
import { useHistory } from "react-router-dom";
import { useDispatch } from "react-redux";
const SingleUser = () => {
const history = useHistory();
const dispatch = useDispatch();
const logout = () => {
dispatch({ type: "LOGOUT" });
history.push("/");
history.go("/");
};
return <div onClick={logout}>Single User</div>;
};
export default SingleUser;

React-Router V2, V4 - How can I use "router" of react-router V2 in react-router V4? [duplicate]

In the current version of React Router (v3) I can accept a server response and use browserHistory.push to go to the appropriate response page. However, this isn't available in v4, and I'm not sure what the appropriate way to handle this is.
In this example, using Redux, components/app-product-form.js calls this.props.addProduct(props) when a user submits the form. When the server returns a success, the user is taken to the Cart page.
// actions/index.js
export function addProduct(props) {
return dispatch =>
axios.post(`${ROOT_URL}/cart`, props, config)
.then(response => {
dispatch({ type: types.AUTH_USER });
localStorage.setItem('token', response.data.token);
browserHistory.push('/cart'); // no longer in React Router V4
});
}
How can I make a redirect to the Cart page from function for React Router v4?
You can use the history methods outside of your components. Try by the following way.
First, create a history object used the history package:
// src/history.js
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
Then wrap it in <Router> (please note, you should use import { Router } instead of import { BrowserRouter as Router }):
// src/index.jsx
// ...
import { Router, Route, Link } from 'react-router-dom';
import history from './history';
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/login">Login</Link></li>
</ul>
<Route exact path="/" component={HomePage} />
<Route path="/login" component={LoginPage} />
</div>
</Router>
</Provider>,
document.getElementById('root'),
);
Change your current location from any place, for example:
// src/actions/userActionCreators.js
// ...
import history from '../history';
export function login(credentials) {
return function (dispatch) {
return loginRemotely(credentials)
.then((response) => {
// ...
history.push('/');
});
};
}
UPD: You can also see a slightly different example in React Router FAQ.
React Router v4 is fundamentally different from v3 (and earlier) and you cannot do browserHistory.push() like you used to.
This discussion seems related if you want more info:
Creating a new browserHistory won't work because <BrowserRouter> creates its own history instance, and listens for changes on that. So a different instance will change the url but not update the <BrowserRouter>.
browserHistory is not exposed by react-router in v4, only in v2.
Instead you have a few options to do this:
Use the withRouter high-order component
Instead you should use the withRouter high order component, and wrap that to the component that will push to history. For example:
import React from "react";
import { withRouter } from "react-router-dom";
class MyComponent extends React.Component {
...
myFunction() {
this.props.history.push("/some/Path");
}
...
}
export default withRouter(MyComponent);
Check out the official documentation for more info:
You can get access to the history object’s properties and the closest <Route>'s match via the withRouter higher-order component. withRouter will re-render its component every time the route changes with the same props as <Route> render props: { match, location, history }.
Use the context API
Using the context might be one of the easiest solutions, but being an experimental API it is unstable and unsupported. Use it only when everything else fails. Here's an example:
import React from "react";
import PropTypes from "prop-types";
class MyComponent extends React.Component {
static contextTypes = {
router: PropTypes.object
}
constructor(props, context) {
super(props, context);
}
...
myFunction() {
this.context.router.history.push("/some/Path");
}
...
}
Have a look at the official documentation on context:
If you want your application to be stable, don't use context. It is an experimental API and it is likely to break in future releases of React.
If you insist on using context despite these warnings, try to isolate your use of context to a small area and avoid using the context API directly when possible so that it's easier to upgrade when the API changes.
Now with react-router v5 you can use the useHistory hook like this:
import { useHistory } from "react-router-dom";
function HomeButton() {
let history = useHistory();
function handleClick() {
history.push("/home");
}
return (
<button type="button" onClick={handleClick}>
Go home
</button>
);
}
read more at: https://reacttraining.com/react-router/web/api/Hooks/usehistory
Simplest way in React Router 4 is to use
this.props.history.push('/new/url');
But to use this method, your existing component should have access to history object. We can get access by
If your component is linked to Route directly, then your component already has access to history object.
eg:
<Route path="/profile" component={ViewProfile}/>
Here ViewProfile has access to history.
If not connected to Route directly.
eg:
<Route path="/users" render={() => <ViewUsers/>}
Then we have to use withRouter, a heigher order fuction to warp the existing component.
Inside ViewUsers component
import { withRouter } from 'react-router-dom';
export default withRouter(ViewUsers);
That's it now, your ViewUsers component has access to history object.
UPDATE
2- in this scenario, pass all route props to your component, and then we can access this.props.history from the component even without a HOC
eg:
<Route path="/users" render={props => <ViewUsers {...props} />}
This is how I did it:
import React, {Component} from 'react';
export default class Link extends Component {
constructor(props) {
super(props);
this.onLogout = this.onLogout.bind(this);
}
onLogout() {
this.props.history.push('/');
}
render() {
return (
<div>
<h1>Your Links</h1>
<button onClick={this.onLogout}>Logout</button>
</div>
);
}
}
Use this.props.history.push('/cart'); to redirect to cart page it will be saved in history object.
Enjoy, Michael.
According to React Router v4 documentation - Redux Deep Integration session
Deep integration is needed to:
"be able to navigate by dispatching actions"
However, they recommend this approach as an alternative to the "deep integration":
"Rather than dispatching actions to navigate you can pass the history object provided to route components to your actions and navigate with it there."
So you can wrap your component with the withRouter high order component:
export default withRouter(connect(null, { actionCreatorName })(ReactComponent));
which will pass the history API to props. So you can call the action creator passing the history as a param. For example, inside your ReactComponent:
onClick={() => {
this.props.actionCreatorName(
this.props.history,
otherParams
);
}}
Then, inside your actions/index.js:
export function actionCreatorName(history, param) {
return dispatch => {
dispatch({
type: SOME_ACTION,
payload: param.data
});
history.push("/path");
};
}
Nasty question, took me quite a lot of time, but eventually, I solved it this way:
Wrap your container with withRouter and pass history to your action in mapDispatchToProps function. In action use history.push('/url') to navigate.
Action:
export function saveData(history, data) {
fetch.post('/save', data)
.then((response) => {
...
history.push('/url');
})
};
Container:
import { withRouter } from 'react-router-dom';
...
const mapDispatchToProps = (dispatch, ownProps) => {
return {
save: (data) => dispatch(saveData(ownProps.history, data))}
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Container));
This is valid for React Router v4.x.
I offer one more solution in case it is worthful for someone else.
I have a history.js file where I have the following:
import createHistory from 'history/createBrowserHistory'
const history = createHistory()
history.pushLater = (...args) => setImmediate(() => history.push(...args))
export default history
Next, on my Root where I define my router I use the following:
import history from '../history'
import { Provider } from 'react-redux'
import { Router, Route, Switch } from 'react-router-dom'
export default class Root extends React.Component {
render() {
return (
<Provider store={store}>
<Router history={history}>
<Switch>
...
</Switch>
</Router>
</Provider>
)
}
}
Finally, on my actions.js I import History and make use of pushLater
import history from './history'
export const login = createAction(
...
history.pushLater({ pathname: PATH_REDIRECT_LOGIN })
...)
This way, I can push to new actions after API calls.
Hope it helps!
this.context.history.push will not work.
I managed to get push working like this:
static contextTypes = {
router: PropTypes.object
}
handleSubmit(e) {
e.preventDefault();
if (this.props.auth.success) {
this.context.router.history.push("/some/Path")
}
}
Be careful that don't use react-router#5.2.0 or react-router-dom#5.2.0 with history#5.0.0. URL will update after history.push or any other push to history instructions but navigation is not working with react-router. use npm install history#4.10.1 to change the history version. see React router not working after upgrading to v 5.
I think this problem is happening when push to history happened. for example using <NavLink to="/apps"> facing a problem in NavLink.js that consume <RouterContext.Consumer>. context.location is changing to an object with action and location properties when the push to history occurs. So currentLocation.pathname is null to match the path.
In this case you're passing props to your thunk. So you can simply call
props.history.push('/cart')
If this isn't the case you can still pass history from your component
export function addProduct(data, history) {
return dispatch => {
axios.post('/url', data).then((response) => {
dispatch({ type: types.AUTH_USER })
history.push('/cart')
})
}
}
I struggled with the same topic.
I'm using react-router-dom 5, Redux 4 and BrowserRouter.
I prefer function based components and hooks.
You define your component like this
import { useHistory } from "react-router-dom";
import { useDispatch } from "react-redux";
const Component = () => {
...
const history = useHistory();
dispatch(myActionCreator(otherValues, history));
};
And your action creator is following
const myActionCreator = (otherValues, history) => async (dispatch) => {
...
history.push("/path");
}
You can of course have simpler action creator if async is not needed
Here's my hack (this is my root-level file, with a little redux mixed in there - though I'm not using react-router-redux):
const store = configureStore()
const customHistory = createBrowserHistory({
basename: config.urlBasename || ''
})
ReactDOM.render(
<Provider store={store}>
<Router history={customHistory}>
<Route component={({history}) => {
window.appHistory = history
return (
<App />
)
}}/>
</Router>
</Provider>,
document.getElementById('root')
)
I can then use window.appHistory.push() anywhere I want (for example, in my redux store functions/thunks/sagas, etc) I had hoped I could just use window.customHistory.push() but for some reason react-router never seemed to update even though the url changed. But this way I have the EXACT instance react-router uses. I don't love putting stuff in the global scope, and this is one of the few things I'd do that with. But it's better than any other alternative I've seen IMO.
If you are using Redux, then I would recommend using npm package react-router-redux. It allows you to dispatch Redux store navigation actions.
You have to create store as described in their Readme file.
The easiest use case:
import { push } from 'react-router-redux'
this.props.dispatch(push('/second page'));
Second use case with Container/Component:
Container:
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import Form from '../components/Form';
const mapDispatchToProps = dispatch => ({
changeUrl: url => dispatch(push(url)),
});
export default connect(null, mapDispatchToProps)(Form);
Component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class Form extends Component {
handleClick = () => {
this.props.changeUrl('/secondPage');
};
render() {
return (
<div>
<button onClick={this.handleClick}/>
</div>Readme file
);
}
}
I was able to accomplish this by using bind(). I wanted to click a button in index.jsx, post some data to the server, evaluate the response, and redirect to success.jsx. Here's how I worked that out...
index.jsx:
import React, { Component } from "react"
import { postData } from "../../scripts/request"
class Main extends Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
this.postData = postData.bind(this)
}
handleClick() {
const data = {
"first_name": "Test",
"last_name": "Guy",
"email": "test#test.com"
}
this.postData("person", data)
}
render() {
return (
<div className="Main">
<button onClick={this.handleClick}>Test Post</button>
</div>
)
}
}
export default Main
request.js:
import { post } from "./fetch"
export const postData = function(url, data) {
// post is a fetch() in another script...
post(url, data)
.then((result) => {
if (result.status === "ok") {
this.props.history.push("/success")
}
})
}
success.jsx:
import React from "react"
const Success = () => {
return (
<div className="Success">
Hey cool, got it.
</div>
)
}
export default Success
So by binding this to postData in index.jsx, I was able to access this.props.history in request.js... then I can reuse this function in different components, just have to make sure I remember to include this.postData = postData.bind(this) in the constructor().
so the way I do it is:
- instead of redirecting using history.push, I just use Redirect component from react-router-dom
When using this component you can just pass push=true, and it will take care of the rest
import * as React from 'react';
import { Redirect } from 'react-router-dom';
class Example extends React.Component {
componentDidMount() {
this.setState({
redirectTo: '/test/path'
});
}
render() {
const { redirectTo } = this.state;
return <Redirect to={{pathname: redirectTo}} push={true}/>
}
}
Use Callback. It worked for me!
export function addProduct(props, callback) {
return dispatch =>
axios.post(`${ROOT_URL}/cart`, props, config)
.then(response => {
dispatch({ type: types.AUTH_USER });
localStorage.setItem('token', response.data.token);
callback();
});
}
In component, you just have to add the callback
this.props.addProduct(props, () => this.props.history.push('/cart'))
React router V4 now allows the history prop to be used as below:
this.props.history.push("/dummy",value)
The value then can be accessed wherever the location prop is available as
state:{value} not component state.
As we have a history already included in react router 5, we can access the same with reference
import React from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
function App() {
const routerRef = React.useRef();
const onProductNav = () => {
const history = routerRef.current.history;
history.push("product");
}
return (
<BrowserRouter ref={routerRef}>
<Switch>
<Route path="/product">
<ProductComponent />
</Route>
<Route path="/">
<HomeComponent />
</Route>
</Switch>
</BrowserRouter>
)
}
step one wrap your app in Router
import { BrowserRouter as Router } from "react-router-dom";
ReactDOM.render(<Router><App /></Router>, document.getElementById('root'));
Now my entire App will have access to BrowserRouter. Step two I import Route and then pass down those props. Probably in one of your main files.
import { Route } from "react-router-dom";
//lots of code here
//somewhere in my render function
<Route
exact
path="/" //put what your file path is here
render={props => (
<div>
<NameOfComponent
{...props} //this will pass down your match, history, location objects
/>
</div>
)}
/>
Now if I run console.log(this.props) in my component js file that I should get something that looks like this
{match: {…}, location: {…}, history: {…}, //other stuff }
Step 2 I can access the history object to change my location
//lots of code here relating to my whatever request I just ran delete, put so on
this.props.history.push("/") // then put in whatever url you want to go to
Also I'm just a coding bootcamp student, so I'm no expert, but I know you can also you use
window.location = "/" //wherever you want to go
Correct me if I'm wrong, but when I tested that out it reloaded the entire page which I thought defeated the entire point of using React.
Create a custom Router with its own browserHistory:
import React from 'react';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';
export const history = createBrowserHistory();
const ExtBrowserRouter = ({children}) => (
<Router history={history} >
{ children }
</Router>
);
export default ExtBrowserRouter
Next, on your Root where you define your Router, use the following:
import React from 'react';
import { /*BrowserRouter,*/ Route, Switch, Redirect } from 'react-router-dom';
//Use 'ExtBrowserRouter' instead of 'BrowserRouter'
import ExtBrowserRouter from './ExtBrowserRouter';
...
export default class Root extends React.Component {
render() {
return (
<Provider store={store}>
<ExtBrowserRouter>
<Switch>
...
<Route path="/login" component={Login} />
...
</Switch>
</ExtBrowserRouter>
</Provider>
)
}
}
Finally, import history where you need it and use it:
import { history } from '../routers/ExtBrowserRouter';
...
export function logout(){
clearTokens();
history.push('/login'); //WORKS AS EXPECTED!
return Promise.reject('Refresh token has expired');
}
you can use it like this as i do it for login and manny different things
class Login extends Component {
constructor(props){
super(props);
this.login=this.login.bind(this)
}
login(){
this.props.history.push('/dashboard');
}
render() {
return (
<div>
<button onClick={this.login}>login</login>
</div>
)
/*Step 1*/
myFunction(){ this.props.history.push("/home"); }
/**/
<button onClick={()=>this.myFunction()} className={'btn btn-primary'}>Go
Home</button>
If you want to use history while passing a function as a value to a Component's prop, with react-router 4 you can simply destructure the history prop in the render attribute of the <Route/> Component and then use history.push()
<Route path='/create' render={({history}) => (
<YourComponent
YourProp={() => {
this.YourClassMethod()
history.push('/')
}}>
</YourComponent>
)} />
Note: For this to work you should wrap React Router's BrowserRouter Component around your root component (eg. which might be in index.js)

React router v4 use declarative Redirect without rendering the current component

I am using a similar code like this to redirect in my app after users logged in. The code looks like the following:
import React, { Component } from 'react'
import { Redirect } from 'react-router'
export default class LoginForm extends Component {
constructor () {
super();
this.state = {
fireRedirect: false
}
}
submitForm = (e) => {
e.preventDefault()
//if login success
this.setState({ fireRedirect: true })
}
render () {
const { from } = this.props.location.state || '/'
const { fireRedirect } = this.state
return (
<div>
<form onSubmit={this.submitForm}>
<button type="submit">Submit</button>
</form>
{fireRedirect && (
<Redirect to={from || '/home'}/>
)}
</div>
)
}
}
Works fine when a successful login has been triggered. But there is the case, that logged in users enter the login page and should be automatically redirected to the "home" page (or whatever other page).
How can I use the Redirect component without rendering the current component and without (as far as I understand discouraged) imperative pushing to the history (e.g. in componentWillMount)?
Solution 1
You could use withRouter HOC to access history via props.
Import withRouter.
import {
withRouter
} from 'react-router-dom';
Then wrap with HOC.
// Example code
export default withRouter(connect(...))(Component)
Now you can access this.props.history. For example use it with componentDidMount().
componentDidMount() {
const { history } = this.props;
if (this.props.authenticated) {
history.push('/private-route');
}
}
Solution 2 Much better
Here is example on reacttraining.
Which would perfectly work for you.
But you just need to create LoginRoute to handle problem you described.
const LoginRoute = ({ component: Component, ...rest }) => (
<Route
{...rest} render={props => (
fakeAuth.isAuthenticated ? (
<Redirect to={{
pathname: '/private-route',
state: { from: props.location }
}} />
) : (
<Component {...props} />
)
)} />
);
and inside <Router /> just replace
<Route path="/login" component={Login}/>
with
<LoginRoute path="/login" component={Login}/>
Now everytime somebody will try to access /login route as authenticated user, he will be redirected to /private-route. It's even better solution because it doesn't mount your LoginComponent if condition isn't met.
Here is another solution which doesn't touch React stuff at all. E.g. if you need to navigate inside redux-saga.
Have file history.js:
import {createBrowserHistory} from 'history';
export default createBrowserHistory();
Somewhere where you define routes, don't use browser router but just general <Router/>:
import history from 'utils/history';
...
<Router history={history}>
<Route path="/" component={App}/>
</Router>
That's it. Now you can use same history import and push new route.
In any part of your app:
import history from 'utils/history';
history.push('/foo');
In saga:
import {call} from 'redux-saga/effects';
import history from 'utils/history';
...
history.push('/foo');
yield call(history.push, '/foo');

Categories

Resources