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>
)
}
Related
I already searched a lot and could not find an answer. In my SolidJs app, the second route is not redered in root element:
import { Routes, Route, useLocation } from "solid-app-router"
import { useNavigate } from 'solid-app-router';
const Login = lazy(() => import("./pages/login"));
const Operation = lazy(() => import("./pages/operation"));
export default function App() {
const navigate = useNavigate();
const location = useLocation();
onMount(() => {
const token = localStorage.getItem('token');
if (!token && location.pathname !== '/') {
navigate("/", { replace: true });
}
if (token && location.pathname === '/') {
navigate("/operations", { replace: true });
}
});
return (
<Routes>
<Route path='/' component={Login} />
<Route path='/operations' component={Operation} />
</Routes>
)
}
Everything looks OK at component Operation and if I call this component in first route like bellow it work:
<Route path='/' component={Operation} />
The root component should be wrapped in Router component.
I struggled with this for a while and realised that the application needs to make reference to the Routes by means of an <A> tag.
If you look at this part of the docs, it mentions that these tags include an active class which seems to be used to tell the application to actually bundle this component on build time otherwise by default it is not included.
So if you put Operations somewhere in your homepage like a Navbar, that page/component should bundle in on deployment.
By default though all routes in Routes in dev environment should work so if it is not working in dev environment I would just check that the module is being resolved correctly.
I am trying to use React Shepherd to create a walkthrough for my application.
I can't seem to find anything that explains how to switch routes within the tour. window.location.replace = "/someurl" refreshes the page and kills the tour completely. I am trying to achieve something along the lines of this
History.js
import { createBrowserHistory } from "history";
const history = createBrowserHistory();
export default history;
steps.js
import hist from "./History";
const Steps = [
{
//...
when: {
hide: () => {
hist.push("/someurl");
},
},
},
//...
]
export default Steps;
App.js
import React from "react";
import { Router } from "react-router";
import { Route } from "react-router-dom";
//...
import Steps from "./Steps";
import hist from "./History";
import "shepherd.js/dist/css/shepherd.css";
const tourOptions = {
defaultStepOptions: {
cancelIcon: {
enabled: true,
},
classes: "shepherd-theme-custom",
},
useModalOverlay: true,
};
const App = () => {
return (
<Router history={hist}>
<Route exact path="/signin" component={SignIn} />
<ShepherdTour steps={Steps} tourOptions={tourOptions}>
<PrivateRoute exact path="/*" component={Main} />
</ShepherdTour>
</Router>
);
};
export default App;
When the steps' hide function is called, the url path is switched but the page is not rendered. I am wondering if I am using react-router wrong or is there a different way to go about this?
So this actually had nothing to do with React Shepherd at all, this was purely a React Router issue. I mistakenly nested two BrowserRouter's since it was also accidentally included in my Main component. Once removed the application navigates with custom history file just fine.
I have followed this tutorial to implement authentication in my gatsby project. The problem is I have first setup the project and the routing is made from the pages folder and then I have implemented the above auth code but it still taking the routes from the pages folder and not from the app.js file. Could someone please help how can I route my components from the app.js instead of using from pages folder.
This is my gatsby-nodejs file
// Implement the Gatsby API “onCreatePage”. This is
// called after every page is created.
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
// page.matchPath is a special key that's used for matching pages
// only on the client.
if (page.path.match(/^\/app/)) {
page.matchPath = "/app/*"
// Update the page.
createPage(page)
}
}
here is src/pages.app.js
import React from "react"
import { Router } from "#reach/router"
import Layout from "../components/layout"
import Home from '../components/dashboard/home/container'
import Login from '../components/marketing/home/pulsemetrics'
import { isLoggedIn } from "../services/auth"
console.log('vvvvvvvvvvvvvvvvvvvvv')
const PrivateRoute = ({ component: Component, location, ...rest }) => {
console.log('hjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiii')
if (!isLoggedIn() && location.pathname !== `/app/login`) {
// If the user is not logged in, redirect to the login page.
navigate(`/app/login`)
return null
}
return <Component {...rest} />
}
const App = () => (
<Layout>
<Router>
<PrivateRoute path="/ddddddddddddddddddd" component={Home} />
<Login path="/" />
</Router>
</Layout>
)
export default App
The paths that you have in your App.js should have /app/ prepended in front of them since your PrivateRoute logic uses that to check for a login. Furthermore what your gatsby-node.js file is really saying is that for routes starting with app it should create a new page. Your src/pages/app.js has the task to define how these pages should be created (since they won't be the usual generated static pages by gatsby)
import React from "react"
import { Router } from "#reach/router"
import Layout from "../components/layout"
import Home from '../components/dashboard/home/container'
import Login from '../components/marketing/home/pulsemetrics'
import { isLoggedIn } from "../services/auth"
console.log('vvvvvvvvvvvvvvvvvvvvv')
const PrivateRoute = ({ component: Component, location, ...rest }) => {
console.log('hjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiii')
if (!isLoggedIn() && location.pathname !== `/app/login`) {
// If the user is not logged in, redirect to the login page.
navigate(`/app/login`)
return null
}
return <Component {...rest} />
}
const App = () => (
<Layout>
<Router>
<PrivateRoute path="/app/home" component={Home} />
<Login path="/app/login" />
</Router>
</Layout>
)
export default App
Read the gatsby client-only routes documentation for reference or have a look at this github issue
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.
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