React router, redux, electron: router isn't rendering - javascript

I'm trying to get react-router to work in an electron app. I'm using a boilerplate and stripped away some of the stuff I don't want but that should provide a pretty solid summary of the technologies being used.
I cannot for the life of my get the router to work as expected. To me this means that when I use history.push('/someroute/') or <Link to="someroute"..., the corresponding component to that router should be rendered and the previous one gone.
Here is my routes file:
export default () => (
<App>
<Switch>
<Route path="/" component={MyDevkitsPage} />
<Route path="/browser/" component={DevkitBrowserPage} />
<Route path="/Console/" component={ConsolePage} />
<Route path="/Controller/:devkitIp" component={ControllerPage} />
<Route path="/Setting/:devkitIp" component={SettingPage} />
</Switch>
</App>
);
I would expect that if <Link to="/browser/"> aaaa </Link> is clicked, it would go to the /browser/ component. Instead, I get an error:
Warning: Hash history cannot PUSH the same path; a new entry will not be added to the history stack
Likewise, if I move the path="/browser/" line above the path="/" line in routes, it will always go to /browser/.
edit: RootContainer
export default function Root({ store, history }: RootType) {
return (
<Provider store={store}>
<ConnectedRouter history={history}>
<Routes />
</ConnectedRouter>
</Provider>
);
}
Edit 2: Configure store
const configureStore = (initialState?: counterStateType) => {
// Redux Configuration
const middleware = [];
const enhancers = [];
// Thunk Middleware
middleware.push(thunk);
// Logging Middleware
const logger = createLogger({
level: 'info',
collapsed: true
});
middleware.push(logger);
// Router Middleware
const router = routerMiddleware(history);
middleware.push(router);
// Redux DevTools Configuration
const actionCreators = {
...counterActions,
...routerActions,
};
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
/* eslint-disable no-underscore-dangle */
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Options: http://zalmoxisus.github.io/redux-devtools-extension/API/Arguments.html
actionCreators,
})
: compose;
/* eslint-enable no-underscore-dangle */
// Apply Middleware & Compose Enhancers
enhancers.push(applyMiddleware(...middleware));
const enhancer = composeEnhancers(...enhancers);
// Create Store
const store = createStore(rootReducer, initialState, enhancer);
if (module.hot) {
module.hot.accept('../reducers', () =>
store.replaceReducer(require('../reducers')) // eslint-disable-line global-require
);
}
return store;
};

You will need to change <Route path="/" component={MyDevkitsPage} /> to <Route exact path="/" component={MyDevkitsPage} />.
By adding exact to that <Route /> you will prevent react-router from trying to load both <MyDevkitsPage /> and <DevkitBrowserPage /> at the same time.

I had the same issue. One thing that I notice is that if I place the route path after all the others, it will not throw the exception.
My Code:
export default () => (
<App>
<Switch>
<Route path="/counter" component={CounterPage} />
<Route path="/calculator" component={Calculator} />
<Route path="/" component={HomePage} />
</Switch>
</App>
);

This code worked for me I've changed BrowseRouter to MemoryRouter
import React from 'react'
import { MemoryRouter as Router, Routes, Route } from 'react-router-dom';
import './App.css';
import routes from './Route';
function App() {
return (
<>
<Router>
<Routes>
{routes.map((route) => {
return <Route path={route.path} element={route.element} />
})}
</Routes>
</Router>
</>
);
}
export default App;

Related

React router history push always route to 404 page

js and facing hard time with react router dom.
I want to change the route when I click a button in my navigation drawer component.
This is my App.js component.
import { Router, Route, Switch } from "react-router-dom";
function App() {
const classes = useStyles();
return (
<Provider store={store}>
<div className={classes.root}>
<NavBar />
<Drawer />
<Router history={history}>
<Switch>
<Route path="/" exact component={Login}></Route>
<Route path="/country" exact component={Country}></Route>
<Route path="/user-create" exact component={User}></Route>
<Route path="/countries" exact component={ListView}></Route>
<Route component={NotFound}></Route>
</Switch>
</Router>
</div>
</Provider>
);
}
And here I pass history prop to the Router component.
History.js
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
Drawer.js
import history from '../../history'
const onRoute = (path) => {
history.push("/user-create");
// props.toggleDrawer();
}
In Drawer.js it is always route to the NotFoundComponet.
Why could this happen?
In your NavBar and Drawer components include the below withRouter or the alt useHistory hook to get access to the history prop.
import { withRouter } from 'react-router'
...
this.props.history.push('/user-create')
...
export default withRouter(...
I'd put <NavBar /> and <Drawer /> inside <Router>, and do
const { push } = useHistory() // from react-router-dom
inside them, to get a reference of the push API

React Router - Cannot read property 'history' of undefined

I am building a styleguide app. I have two dropdown components where a user can choose both a brand and a component - the app will then display the chosen component branded according to the selected brand. I want both of these options to be included in the URL.
The two dropdown's that are programatically changing the route. I am getting the error TypeError: Cannot read property 'history' of undefined whenever the user interacts with either dropdown.
I have a component being rendered by a route :
<Route path="/:brand/:component"
render={props => <DisplayComponent {...props} />} />
That component has two event handlers for two dropdown component that let the user select the root:
handleComponentPick(event: any) {
const component = event.target.value;
this.props.history.push(`/${this.props.match.params.brand}/${component}`);
}
handleBrandChange = (event: any) => {
if (event.target instanceof HTMLElement) {
const brand = event.target.value;
this.props.history.push(`/${brand}/${this.props.match.params.component}`);
}
};
render = () => {
return (
<div className={"constrain-width-wide center "}>
<ThemePicker
component={this.props.match.params.component}
brand={this.props.match.params.brand}
handleBrandChange={this.handleBrandChange}
handleComponentPick={this.handleComponentPick}
/>
<div className="currently-selected-component" />
<Route path="/:brand/button" component={Button} />
<Route path="/:brand/card" component={Card} />
</div>
);
};
}
I am wrapping the whole app in the Router.
ReactDOM.render(
<Router>
<App />
</Router>,
document.getElementById("root")
);```
If you are getting this error inside a test using jest, you need to wrap your componen within a router. I am using react-testing-library, so my logic looks as follows:
import { render, cleanup } from '#testing-library/react'
import { BrowserRouter as Router } from 'react-router-dom'
import YourComponent from '../path/to/YourComponent'
// ...
describe('YourComponent component', () => {
afterEach(cleanup)
it('matches snapshot', () => {
const { asFragment } = render(
// The following will solve this issue
<Router>
<YourComponent />
</Router>
)
expect(asFragment()).toMatchSnapshot()
})
})
Can you try these following changes
handleComponentPick(event: any) { to handleComponentPick = (event: any) => {
then
render = () => { to render() {
Hope this works.
you have to pass the history like
<Router history={browserHistory} routes={routes} />,
that way, you can use history with props to navigate.
font: https://github.com/ReactTraining/react-router/blob/v3/docs/guides/Histories.md
try to use browserHistory on you app.js, like
render(
<Router history={browserHistory}>
<Route path='/' component={App}>
<IndexRoute component={Home} />
<Route path='about' component={About} />
<Route path='features' component={Features} />
</Route>
</Router>,
document.getElementById('app')
)
that way, you are passing history for all of your another router.
We need to pass history as a prop to Router. I am expecting that you are using react router v4 aka react-router-dom.
import { createBrowserHistory } from "history";
import { Router } from "react-router-dom";
const history = createBrowserHistory();
...
<Router history={history}>
<Routes />
</Router>
Demo : https://codesandbox.io/s/yv5y905ojv
Spied on the useHistory() hook and provided the mock route data.
import routeData from 'react-router';
describe('<Component /> container tests', () => {
beforeEach(() => {
const mockHistory = {
pathname: '/dashboard'
};
jest.spyOn(routeData, 'useHistory').mockReturnValue(mockHistory);
});

HashRouter tips error in react router 4.x

Next to the Web Tips Picture
https://i.stack.imgur.com/A2OGn.png
Warning: Hash history cannot PUSH the same path; a new entry will not be added to the history stack
Tips error , This is when I click on the link again
Next to Picture is React-Router File Code...
https://i.stack.imgur.com/WgqqN.png
import { HashRouter, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import View from './containers';
import configureStore from './store/configureStore';
const store = configureStore();
const AppRouter = () => (
<Provider store={store}>
<HashRouter>
<View.App.Container>
<Route path='/' exact={true} component={View.App.Dashboard} />
<Route path='/Todo' component={View.Todo.Container} />
<Route path='/News' render={() => (
<View.News.Container>
<Route path='/News/List' render={() => (
<h2>News List Show</h2>
)} />
</View.News.Container>
)} />
</View.App.Container>
</HashRouter>
</Provider>
);
export default AppRouter;
If you use the component Link for navigation, you may want to set the prop replace on it.
https://reacttraining.com/react-router/web/api/Link/replace-bool

React-Router keeps giving me a warning: You cannot change <Router routes>

I am using React-Router on my application with a personalized history object.
It looks like this:
import { createHistory } from 'history';
import { Router, Route, IndexRedirect, useRouterHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
...
componentWillMount() {
const browserHistory = useRouterHistory(createHistory)({
basename: '/sign',
});
this.history = syncHistoryWithStore(browserHistory, store, {
selectLocationState: (state) => state.routing,
});
}
render() {
const history = this.history;
return (
<Router history={history}>
<Route path="/" component={Sign}>
<IndexRedirect to="/login" />
<Route path="login" component={Login} />
</Route>
</Router>
);
}
So when I access mywebsite.com/sign, it redirects me to mywebsite.com/sign/login which is fine, but I get this error in the console:
Warning: [react-router] You cannot change <Router routes>; it will be ignored
If I access the login page directly, I don't get any error.
Any idea what's wrong?
Thanks
That's probably happening because your "router component" is trying to re-render everytime something changes (probably the history).
It's easier to use a const for your routes
const browserHistory = useRouterHistory(createHistory)({
basename: '/sign',
});
const history = syncHistoryWithStore(browserHistory, store, {
selectLocationState: (state) => state.routing,
});
const routes = (<Route path="/" component={Sign}>
<IndexRedirect to="/login" />
<Route path="login" component={Login} />
</Route>)
ReactDOM.render(
<Router history={history}>
{routes}
</Router>,
yourElement
);

Accessing Redux Store from routes set up via React Router

I would like to make use of react-router's onEnter handler in order to prompt users to authenticate when entering a restricted route.
So far my routes.js file looks something like this:
import React from 'react';
import { Route, IndexRoute } from 'react-router';
export default (
<Route path="/" component={App}>
<IndexRoute component={Landing} />
<Route path="learn" component={Learn} />
<Route path="about" component={About} />
<Route path="downloads" component={Downloads} onEnter={requireAuth} />
</Route>
)
Ideally, I'd like my requireAuth function to be a redux action that has access to the store and current state, that works like this: store.dispatch(requireAuth()).
Unfortunately I don't have access to the store in this file. I don't think I can use really use connect in this case to access the relevant actions that I want. I also can't just import store from the file where the store is created, as this is undefined when the app first loads.
The easiest way to accomplish this is to pass your store to a function that returns your routes (rather than return your routes directly). This way you can access the store in onEnter and other react router methods.
So for your routes:
import React from 'react';
import { Route, IndexRoute } from 'react-router';
export const getRoutes = (store) => (
const authRequired = (nextState, replaceState) => {
// Now you can access the store object here.
const state = store.getState();
if (!state.user.isAuthenticated) {
// Not authenticated, redirect to login.
replaceState({ nextPathname: nextState.location.pathname }, '/login');
}
};
return (
<Route path="/" component={App}>
<IndexRoute component={Landing} />
<Route path="learn" component={Learn} />
<Route path="about" component={About} />
<Route path="downloads" component={Downloads} onEnter={authRequired} />
</Route>
);
)
Then update your main component to call the getRoutes function, passing in the store:
<Provider store={ store }>
<Router history={ history }>
{ getRoutes(store) }
</Router>
</Provider>
As for dispatching an action from requireAuth, you could write your function like this:
const authRequired = (nextState, replaceState, callback) => {
store.dispatch(requireAuth()) // Assume this action returns a promise
.then(() => {
const state = store.getState();
if (!state.user.isAuthenticated) {
// Not authenticated, redirect to login.
replaceState({ nextPathname: nextState.location.pathname }, '/login');
}
// All ok
callback();
});
};
Hope this helps.
If you want that you could write route.js like this:
var requireAuth = (store, nextState, replace) => {
console.log("store: ", store);
//now you have access to the store in the onEnter hook!
}
export default (store) => {
return (
<Route path="/" component={App}>
<IndexRoute component={Landing} />
<Route path="learn" component={Learn} />
<Route path="about" component={About} />
<Route path="downloads" component={Downloads} onEnter={requireAuth.bind(this, store)} />
</Route>
);
);
I've setup an example which you could play with in this codepen.
Not sure if triggering an action in order to handle the auth is a good idea. Personally I prefer handling auth in a different way:
Instead of using an onEnter hook, I use a wrapping function. I want the admin section of my blog protected, therefore I wrapped the AdminContainer component in the routes with a function, requireAuthentication, see below.
export default (store, history) => {
return (
<Router history={history}>
<Route path="/" component={App}>
{ /* Home (main) route */ }
<IndexRoute component={HomeContainer}/>
<Route path="post/:slug" component={PostPage}/>
{ /* <Route path="*" component={NotFound} status={404} /> */ }
</Route>
<Route path="/admin" component={requireAuthentication(AdminContainer)}>
<IndexRoute component={PostList}/>
<Route path=":slug/edit" component={PostEditor}/>
<Route path="add" component={PostEditor}/>
</Route>
<Route path="/login" component={Login}/>
</Router>
);
};
requireAuthentication is a function that
if the user is authenticated, renders the wrapped component,
otherwise redirects to Login
You can see it below:
export default function requireAuthentication(Component) {
class AuthenticatedComponent extends React.Component {
componentWillMount () {
this.checkAuth();
}
componentWillReceiveProps (nextProps) {
this.checkAuth();
}
checkAuth () {
if (!this.props.isAuthenticated) {
let redirectAfterLogin = this.props.location.pathname;
this.context.router.replace({pathname: '/login', state: {redirectAfterLogin: redirectAfterLogin}});
}
}
render () {
return (
<div>
{this.props.isAuthenticated === true
? <Component {...this.props}/>
: null
}
</div>
)
}
}
const mapStateToProps = (state) => ({
isAuthenticated: state.blog.get('isAuthenticated')
});
AuthenticatedComponent.contextTypes = {
router: React.PropTypes.object.isRequired
};
return connect(mapStateToProps)(AuthenticatedComponent);
}
Also, requireAuthentication will protect all routes under /admin. And you can reuse it wherever you like.
Lots have changed over the time. onEnter no longer exists on react-router-4
The following is from my real project for your reference
export const getRoutes = (store) => {
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
checkIfAuthed(store) ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/login'
}}/>
)
)}/>
)
return (
<Router>
<div>
<PrivateRoute exact path="/" component={Home}/>
<Route path="/login" component={Login} />
</div>
</Router>
)
}
After trying out a few of the above suggestions, I found the best way to track the state of your store with updates is to use React-Redux's useSelector function which basically connects a functional component to the store.
import * as React from "react";
import {Redirect, Route, Switch} from "react-router";
import {Provider, useSelector} from "react-redux";
import { createBrowserHistory } from "history";
// Your imports
import {IApplicationState,} from "./store/store";
import {Login} from "./routes/login/login.component";
import {getToken} from "./store/helpers/httpHelpers";
function handleRedirect() {
if(!getToken()) {
return <Redirect to="/login"/>;
}
}
const restricted = (Component: _ComponentType, isLoggedIn: boolean) => {
// Don't redirect here if there is a token in localStorage.
// This is happening when we are on a restricted route and the user
// refreshes & the isLoggedIn state hasn't been updated yet.
return !isLoggedIn ? (
() => handleRedirect()
) : () => <Route component={Component}/>
};
const AuthenticateRoutes = () => {
const isLoggedIn = useSelector((state: IApplicationState) => state.auth.isLoggedIn);
return (
<Switch>
<Route path="/login" component={Login} />
<Route path="/downloads" render={restricted(Download, isLoggedIn)} />
</Switch>
);
};
export function App() {
return (
<Provider store={store}>
<>
<Router history={createBrowserHistory()}>
<AuthenticateRoutes />
</Router>
</>
</Provider>
);
}

Categories

Resources