I came across an issue using react-router ^2.4.1 where if I scroll down on my home page and afterwards go to a new page it will also be scrolled down, as opposed to being at the top (expected behaviour).
I am using this starter pack: react-webpack-node and my routes.jsx looks like this
import React from 'react'
import { Route, IndexRoute } from 'react-router'
import cookie from 'react-cookie'
import App from 'containers/App'
import HomePage from 'containers/HomePage'
import WaitingListPage from 'containers/WaitingListPage'
import NotFoundPage from 'containers/NotFoundPage'
import SupportPage from 'containers/SupportPage'
/*
* #param {Redux Store}
* We require store as an argument here because we wish to get
* state from the store after it has been authenticated.
*/
export default (store) => {
const hasQueueToken = (nextState, replace, callback) => {
if (cookie.load('queueToken')) {
replace({
pathname: `/waiting-list/${cookie.load('queueToken')}`,
state: { nextPathname: nextState.location.pathname }
})
}
callback()
}
return (
<Route path='/' component={App}>
<IndexRoute component={HomePage} />
<Route path='/w_ref/:ref' component={HomePage} />
<Route path='/waiting-list/:token' component={WaitingListPage} />
<Route path='/waiting-list' onEnter={hasQueueToken} component={WaitingListPage} />
<Route path='/support' component={SupportPage} />
<Route path='/terms-and-conditions' component={TermsConditions} />
<Route path='/privacy-policy' component={PrivacyPolicy} />
<Route path='*' component={NotFoundPage} />
</Route>
)
}
React Router does not include scroll state management starting in version 2.0.0.
The recommended approach is to decorate the router with scroll-behavior using react-router-scroll as seen in this example:
import { applyRouterMiddleware, browserHistory, Router } from 'react-router';
import useScroll from 'react-router-scroll';
/* ... */
ReactDOM.render(
<Router
history={browserHistory}
routes={routes}
render={applyRouterMiddleware(useScroll())}
/>,
container
);
#John Trichereau:
scrolling to bottom can be done by giving a callback to useScroll, your callback would look like:
function customScroll (prevRouterProps, location) {
// on route /foo scroll to bottom
if (location.pathname == '/foo') return 'fooBottomDiv';
// on all other routes, follow useScroll default behavior
return prevRouterProps && location.pathname !== prevRouterProps.location.pathname;
}
and you would pass it to your router like this:
<Router render={ applyRouterMiddleware(useScroll((prevRouterProps, { location }) => customScroll(prevRouterProps, location))) }>
In your page, you would need to insert an invisible div with id fooBottomDiv. If you don't want to insert such a div, then you can have customScroll return a 2-element array [x, y] which could be [0, Number.MAX_SAFE_INTEGER] to scroll to bottom.
Documentation here
Note however that if your page has a component which loads data and displays it, it will most likely not work as the customScroll function is only called upon route matching, whereas your data is probably called asynchronously and received after route matching.
In this case, it's most convenient to use
ReactDOM.findDOMNode(this.refs.fooBottomDiv).scrollIntoView({ behavior: 'smooth' });
in React lifecycle methods componentDidMount and componentDidUpdate, where your component would contain an empty div with ref attribute ref='fooBottomDiv.
Related
below is my app.js code which will show the components based on the url. when user goes to /home, i need to perform some cookie operations. which i'm able to do. Since home component is required for more than one url, every url(/new,/dashboard) performs the cookie operation. Is it normal behavior ?
Below is my code, please let me know if this is react way to write it or not. I'm new to react js.
function App() {
return (
<div className="App">
<Router>
<Navbar />
<Switch>
<Route path="/" exact />
</Switch>
<Route path="/Home">
<Home />
<About />
</Route>
<Route path="/New">
<Home />
<New />
</Route>
<Route path="/Dashboard">
<Home />
<Dashboard />
</Route>
<Route path="/Run/:testid/">
<Run>
</Route>
</Router>
</div>
);
}
export default App;
below is my home component :
import { React, useEffect } from "react";
import { Button } from "./Button";
import { Link } from "react-router-dom";
import "./Navbar.css";
import "./Home.css";
import useQuery from "../hooks/useQuery";
function Home(props) {
const q = useQuery();
console.log(q.get("token"));
//save the token in cookie if url is /home, but since this home component is render for other urls like /new, /dashboard, it gets executed there as well.
return (
<main id="maincontent">
<div className="dashboard-btns">
<Link to="/dashboard" className="btn-mobile">
<Button id="dashboard" buttonStyle="btn--secondary">
Dashboard
</Button>
</Link>
<Link to="/new" className="btn-mobile">
<Button id="new" buttonStyle="btn--secondary">
New
</Button>
</Link>
<hr className="hr-line" />
</div>
</main>
);
}
export default Home;
In the < Home > component save the token in cookie only if url is /home, but since this home component is rendered for other urls like /new, /dashboard, it gets executed there as well. How do i make it get executed only for /home url
If you want the Home component to render on several routes then you can refactor your routes as such, and specify an array of paths for the route rendering Home. The Router component inclusively matches and render routes, in other words, it renders all matching routes.
function App() {
return (
<div className="App">
<Router>
<Navbar />
<Switch>
<Route path="/" exact /> // what is this for?
</Switch>
<Route path={["/Home", "/New", "/Dashboard"]}>
<Home />
</Route>
<Route path="/Home">
<About />
</Route>
<Route path="/New">
<New />
</Route>
<Route path="/Dashboard">
<Dashboard />
</Route>
<Route path="/Run/:testid/">
<Run>
</Route>
</Router>
</div>
);
}
Update
I'm doing some cookie storing and fetching operation in
component. But that should happen only on "/home" url and shouldn't
happen on "/new", "/dashboard" url. How do I do that?
You can check the current path match and issue a side-effect to do the cookie logic on only the "/home" path.
useRouteMatch
The useRouteMatch hook attempts to match the current URL in the same
way that a <Route> would. It’s mostly useful for getting access to the
match data without actually rendering a <Route>.
match will be null if path isn't a match, otherwise it will return a match object.
import { React, useEffect } from "react";
...
import { Link, useRouteMatch } from "react-router-dom";
...
import useQuery from "../hooks/useQuery";
function Home(props) {
const q = useQuery();
console.log(q.get("token"));
const homeMatch = useRouteMatch("/home");
useEffect(() => {
if (homeMatch) {
//save the token in cookie if url is /home and token defined
const token = q.get("token")
}
}, [homeMatch, q]);
return (
...
);
}
First thing is you can use "exact" to prevent that behaviour, Second you should not use two components inside route, what you can do is you can import Home inside New and Dashboard and use it there.
app.js
markdown.js
importing a component
using the imported component
I followed the Auth0 React Authentication guide written here:
https://auth0.com/blog/complete-guide-to-react-user-authentication
And implemented the ProtectedRoute component as outlined in the tutorial:
import React from "react";
import { Route } from "react-router-dom";
import { withAuthenticationRequired } from "#auth0/auth0-react";
import { Loading } from "../components/index";
const ProtectedRoute = ({ component, ...args }) => (
<Route
component={withAuthenticationRequired(component, {
onRedirecting: () => <Loading />,
})}
{...args}
/>
);
export default ProtectedRoute;
But now I am having an issue with the ProtectedRoute component that doesn't exist if I use withAuthenticationRequired directly in the export statement of the component that I am trying to protect. I have a web app that contains routes like the following:
<Router>
{isAuthenticated && <Header />}
<Switch>
<Route exact path='/'>
{isAuthenticated ? <Redirect to="/home" /> : <LandingPage />}
</Route>
<ProtectedRoute path='/home' component={Home}/>
<ProtectedRoute path='/events' component={Events}/>
<ProtectedRoute path='/dates' component={Dates}/>
</Switch>
</Router>
And my Home component contains something like the following:
function Home(){
return <div className="home-page">
<Sidebar />
<ProtectedRoute path={"/home/dogs"} component={Dogs}/>
<ProtectedRoute path={"/home/cats"} component={Cats}/>
</div>
}
export default Home;
The bug also happens when the Home component doesn't use ProtectedRoute like so:
function Home(){
return <div className="home-page">
<Sidebar />
<Route path={"/home/dogs"} component={Dogs}/>
<Route path={"/home/cats"} component={Cats}/>
</div>
}
export default Home;
I can't explain why it happens, but it prevents the state within the Sidebar component from changing the sidebar's appearance and rendered components.
Here is a link to a codesandbox on how the sidebar should work (no auth0).
https://codesandbox.io/s/react-routing-problem-2efic
When using ProtectedRoute as in the code above, the active class on the navbar links changes, but the rest of the content stays the same.
However, if I instead take the ProtectedRoute off of the Home component, but use withAuthenticationRequired on the export of the Home component, like so:
export default withAuthenticationRequired(Home, {
onRedirecting: () => (<div>Redirecting you to the login page...</div>)
});
and
<Route path='/home' component={Home}/> //instead of ProtectedRoute
Then everything works as it should.
My questions are:
Why is the ProtectedRoute component behaving differently from when withAuthenticationRequired is at the export level?
Do I need to protect routes that are nested within a protected route?
Thanks for any help!
I have a website made with Docusaurus v2 that currently contains documentation. However, I would like to add a page of a list of workflows where if a workflow in the list is clicked, the user would be shown a page of additional details of that workflow. For now it seems docusaurus.config seems to be handling most of the routing, but is there a way I can add a dynamic route like /workflows/:id? I made a separate standalone app which had a Router object and it worked if my App.js looks like this:
// App.js
import Navigation from './Navigation'
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom';
function App() {
return (
<Router>
<Navigation />
<Switch>
<Route path="/" exact component={Home}></Route>
<Route path="/workflows" exact component={Workflows}></Route>
<Route path="/workflows/:id" component={WorkflowItem}></Route>
</Switch>
</Router>
)
}
Is it possible to add the Router somewhere in Docusaurus?
Thanks!
I solved this by creating a simple plugin to add my own custom routes. Documentation here.
Let's call the plugin plugin-dynamic-routes.
// {SITE_ROOT_DIR}/plugin-dynamic-routes/index.js
module.exports = function (context, options) {
return {
name: 'plugin-dynamic-routes',
async contentLoaded({ content, actions }) {
const { routes } = options
const { addRoute } = actions
routes.map(route => addRoute(route))
}
}
}
// docusaurus.config.js
const path = require('path')
module.exports = {
// ...
plugins: [
[
path.resolve(__dirname, 'plugin-dynamic-routes'),
{ // this is the options object passed to the plugin
routes: [
{ // using Route schema from react-router
path: '/workflows',
exact: false, // this is needed for sub-routes to match!
component: '#site/path/to/component/App'
}
]
}
],
],
}
You may be able to use the above method to configure sub-routes as well but I haven't tried it. For the custom page, all you need is the Switch component (you are technically using nested routes at this point). The Layout component is there to integrate the page into the rest of the Docusaurus site.
// App.js
import React from 'react'
import Layout from '#theme/Layout'
import { Switch, Route, useRouteMatch } from '#docusaurus/router'
function App() {
let match = useRouteMatch()
return (
<Layout title="Page Title">
<Switch>
<Route path={`${match.path}/:id`} component={WorkflowItem} />
<Route path={match.path} component={Workflows} />
</Switch>
</Layout>
)
}
I'm trying to change routes depending on logged in state:
renderRouter() {
if (loggedIn) {
return (
<Router>
<Route path="/" component={Dashboard} />
</Router>
);
}
return (
<Router>
<Route path="/" component={Login} />
</Router>
);
}
But when state changes I'm receiving a warning: Warning: [react-router] You cannot change <Router routes>; it will be ignored
Is it possible to reinitialize react-router with new routes?
I know that I could use onEnter to ensure that user has access to this page, but I need to have different components in one route according to logged in state and don't want to move such logic inside of components.
First of all you create two Router, I don't think you should do this.
Try wrapping your Routes in a Switch component which is in only one Router, then use the render props of your "main" Route, which will redirect you if the condition is true, use exact props to be sure this Route will match by default, notice that your "/dashboard" Route is above the other, so the Switch can match it.
The result should be like this :
<Router>
<Switch>
<Route path="/dashboard" component={Dashboard} />
<Route
path="/"
exact
render={() => {
if (loggedIn) {
return <Redirect to="/dashboard" />;
}
return <Login />;
}}
/>
</Switch>
</Router>
Don't forget to import the components.
Hope it helped.
I've tried few times to gain desired behavior and after all decided to change an approach of secure endpoints management. Components on my endpoints are very simple scenes which are just compose layer and some scene modules. So I've created a scene wrapper:
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Login from '../scenes/Login';
function Scene({ children, fallback, secure, auth }) {
if (secure === auth) {
return children;
}
return React.createElement(fallback);
}
Scene.propTypes = {
children: PropTypes.node.isRequired,
auth: PropTypes.bool,
fallback: PropTypes.func,
secure: PropTypes.bool,
};
Scene.defaultProps = {
auth: false,
fallback: Login,
secure: false,
};
const mapStateToProps = ({ auth }) => ({ auth });
export default connect(mapStateToProps)(Scene);
And then in Dashboard scene:
import React from 'react';
import Scene from '../modules/Scene';
import Layout from '../components/Layout';
export default function Dashboard() {
return (
<Scene secure>
<Layout>
<Module1 />
<Module2 />
</Layout>
</Scene>
);
}
I am trying to use the 1.0.0-rc1 react-router and history 2.0.0-rc1 to navigate manually through the website after pressing the button. Unfortunately, after pressing the button I get:
Cannot read property 'pushState' of undefined
My router code:
import React from 'react';
import { Router, Route, Link, browserHistory } from 'react-router'
import AppContainer from './components/AppContainer.jsx';
import MyTab from './components/test/MyTab.jsx';
import MainTab from './components/test/MainTab.jsx';
var routes = (
<Route component={AppContainer} >
<Route name="maintab" path="/" component={MainTab} />
<Route name="mytab" path="/mytab" component={MyTab} />
</Route>
);
React.render(<Router history={browserHistory}>{routes}</Router>, document.getElementById('main'));
The navigation button is on MyTab and it attemps to navigate to MainTab:
import React from 'react';
import 'datejs';
import History from "history";
export default React.createClass({
mixins: [ History ],
onChange(state) {
this.setState(state);
},
handleClick() {
this.history.pushState(null, `/`)
},
render() {
return (
<div className='container-fluid' >
<button type="button" onClick={this.handleClick.bind(this)}>TEST</button>
</div>
);
}
});
When I use history with this.props.history everything works fine. What is the problem with this code?
EDIT.
After adding the following:
const history = createBrowserHistory();
React.render(<Router history={history}>{routes}</Router>, document.getElementById('main'));
I try to access my app. Before (without history={history}), I just accessed localhost:8080/testapp and everything worked fine - my static resources are generated into dist/testapp directory. Now under this URL I get:
Location "/testapp/" did not match any resources
I tried to use the useBasename function in a following way:
import { useBasename } from 'history'
const history = useBasename(createBrowserHistory)({
basename: '/testapp'
});
React.render(<Router history={history}>{routes}</Router>, document.getElementById('main'));
and the application is back, but again I get the error
Cannot read property 'pushState' of undefined
in the call:
handleClick() {
this.history.pushState(null, `/mytab`)
},
I thougt it may be because of my connect task in gulp, so I have added history-api-fallback to configuration:
settings: {
root: './dist/',
host: 'localhost',
port: 8080,
livereload: {
port: 35929
},
middleware: function(connect, opt){
return [historyApiFallback({})];
}
}
But after adding middleware all I get after accessing a website is:
Cannot GET /
As of "react-router": "^4.1.1", you may try the following:
Use 'this.props.history.push('/new-route')'. Here's a detailed example
1: Index.js
import { BrowserRouter, Route, Switch } from 'react-router-dom';
//more imports here
ReactDOM.render(
<div>
<BrowserRouter>
<Switch>
<Route path='/login' component={LoginScreen} />
<Route path='/' component={WelcomeScreen} />
</Switch>
</BrowserRouter>
</div>, document.querySelector('.container'));
Above, we have used BrowserRouter, Route and Switch from 'react-router-dom'.
So whenever you add a component in the React Router 'Route', that is,
<Route path='/login' component={LoginScreen} />
..then 'React Router' will add a new property named 'history' to this component (LoginScreen, in this case). You can use this history prop to programatically navigate to other rountes.
So now in the LoginScreen component you can navigate like this:
2: LoginScreen:
return (
<div>
<h1> Login </h1>
<form onSubmit={this.formSubmit.bind(this)} >
//your form here
</form>
</div>
);
formSubmit(values) {
// some form handling action
this.props.history.push('/'); //navigating to Welcome Screen
}
Because everything changes like hell in react world here's a version which worked for me at December 2016:
import React from 'react'
import { Router, ReactRouter, Route, IndexRoute, browserHistory } from 'react-router';
var Main = require('../components/Main');
var Home = require('../components/Home');
var Dialogs = require('../components/Dialogs');
var routes = (
<Router history={browserHistory}>
<Route path='/' component={Main}>
<IndexRoute component={Home} />
<Route path='/dialogs' component={Dialogs} />
</Route>
</Router>
);
module.exports = routes
To create browser history you now need to create it from the History package much like you've tried.
import createBrowserHistory from 'history/lib/createBrowserHistory';
and then pass it to the Router like so
<Router history={createBrowserHistory()}>
<Route />
</Router>
The docs explain this perfectly