Using React Router to control independent components - reactjs

I am new to React/Redux and I'm struggling with controlling my app layout with React Router (What I am doing maybe beyond the realms of React Router). I have the following code setup:
INDEX.JS
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<Router history={ browserHistory } routes={ routes } />
</Provider>
, document.querySelector('#root'));
APP.JS
class App extends Component {
render() {
return (
<div>
<TopNavigation />
<SideNavigation />
<Stage />
</div>
);
}
}
SIDENAVIGATION.JS
render() {
const openSideNavDrawer = classNames({
'side-navigation-drawer': true,
'open': this.state.sideNavIsOpen
});
return (
<nav className="side-navigation">
<ul onMouseOver={ this.openSideNavDrawer.bind( this ) } >
<li>
<Link to="/project">1</Link>
</li>
<li>
<Link to="/pages">2</Link>
</li>
<li>
<Link to="/blocks">3</Link>
</li>
<li>
<Link to="/reorder">4</Link>
</li>
<li>
<Link to="/download">5</Link>
</li>
</ul>
<div className={ openSideNavDrawer } onMouseOut={ this.closeSideNavDrawer.bind( this ) } >
{ this.props.children }
</div>
</nav>
);
}
ROUTER.JS
import { Route, IndexRoute } from 'react-router';
export default (
<Route path="/" component={ App }>
<Route component={ SideNavigation }>
<Route path="/project" component={ Project } />
<Route path="/pages" component={ Pages } />
<Route path="/blocks" component={ Blocks } />
<Route path="/reorder" component={ Reorder } />
<Route path="/download" component={ Download } />
</Route>
</Route>
);
WIREFRAME WITH EXPLANATION
So far I have only attempted to wire the side navigation, I am receiving no errors but the routing is not working.
Please can anyone explain if:
a/ This is even possible with React Router?
b/ If it is possible what am I doing wrong?
Many thanks in advance.

Related

how to use router inside a routed component

My App.js:
<Router>
<Header/>
<Switch>
<Route exact path="/" component={HomeScreen} />
<Route exact path="/screenOne" component={OneScreen} />
<Route exact path="/screenTwo" component={TwoScreen} />
</Switch>
</Router>
The <Header /> has three links to the respective components viz. HomeScreen, OneScreen, TwoScreen.
I want my <TwoScreen /> to be exactly like this baseComponent(i.e App.js) where I have some links and when I click those links, the components corresponding to the link/path gets rendered.
What is the best way to approach this?
there is an example on the react-router site that meets your need, under nesting, you can check it out https://reactrouter.com/web/example/nesting (for just web)
But for me, I simply declare them all in one place/component, especially on react-router-native, if you're doing react-native.
below is the example from their website for web
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useParams,
useRouteMatch
} from "react-router-dom";
// Since routes are regular React components, they
// may be rendered anywhere in the app, including in
// child elements.
//
// This helps when it's time to code-split your app
// into multiple bundles because code-splitting a
// React Router app is the same as code-splitting
// any other React app.
export default function NestingExample() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
</ul>
<hr />
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/topics">
<Topics />
</Route>
</Switch>
</div>
</Router>
);
}
function Home() {
return (
<div>
<h2>Home</h2>
</div>
);
}
function Topics() {
// The `path` lets us build <Route> paths that are
// relative to the parent route, while the `url` lets
// us build relative links.
let { path, url } = useRouteMatch();
return (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${url}/rendering`}>Rendering with React</Link>
</li>
<li>
<Link to={`${url}/components`}>Components</Link>
</li>
<li>
<Link to={`${url}/props-v-state`}>Props v. State</Link>
</li>
</ul>
<Switch>
<Route exact path={path}>
<h3>Please select a topic.</h3>
</Route>
<Route path={`${path}/:topicId`}>
<Topic />
</Route>
</Switch>
</div>
);
}
function Topic() {
// The <Route> that rendered this component has a
// path of `/topics/:topicId`. The `:topicId` portion
// of the URL indicates a placeholder that we can
// get from `useParams()`.
let { topicId } = useParams();
return (
<div>
<h3>{topicId}</h3>
</div>
);
}

When matching url nested router not working

I have tried for several hours now but without success to identify the reason why this nested router does not work. In the following example in the main component a series of links are listed, among them /surveys that when matching will show the <Surveys /> component, within this component when the url matches /surveys/:id the <Answers /> component should be shown but it never shows. Could you help me identify the reason for this behavior
import ...
function App() {
// some logic
return (
<div>
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/employees">Employees</Link>
</li>
<li>
<Link to="/surveys">Surveys</Link>
</li>
<li>
<Link to="/survey-creator">Survey creator</Link>
</li>
</ul>
</nav>
</div>
<Switch>
<Route path="/survey-creator">
<MySurveyCreator />
</Route>
<Route path="/employees">
<Employees employees={state.employees} />
</Route>
<Route path="/surveys" exact={true}>
<Surveys />
</Route>
<Route path="/" exact={true}>
<Home />
</Route>
</Switch>
</Router>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
Surveys
import ...
function SingleSurvey({ id, name }) {
return (
<li>
<Link to={`/surveys/${id}`}>{name}</Link>
</li>
);
}
export default function Surveys() {
// some logic
return (
<div>
<h1>Survey</h1>
<Switch>
<ul>
{surveys.map(survey => (
<SingleSurvey key={survey.id} {...survey} />
))}
</ul>
<Route exact={true} path={`/${match.path}/:id`}>
<Answers />
</Route>
</Switch>
</div>
);
}
Answers
import React from "react";
import { useParams } from "react-router-dom";
export default function Answers() {
const { id } = useParams();
console.log(id);
return (
<div>
<h1>Answer</h1>
<h2>Current id is: {id}</h2>
</div>
);
}
Your nested route is in Serveys Component, So The Serveys Component has to match the url for the nested route but you put an exact keyword for it that won't match with the /surveys/:id
So in result you won't have a Serveys Component that renders <Answers /> Component.
No Servey No Answer :)
just change this:
<Route path="/surveys" exact={true}>
<Surveys />
</Route>
to this:
<Route path="/surveys">
<Surveys />
</Route>

react js router route to outside router in nested router

I have a problem with nested react-router-dom. I want to get out of inner router to outer router. I don't know how to explain it, so I bring a example here.
https://codesandbox.io/s/react-router-preventing-transitions-forked-vmt4c?fontsize=14&hidenavigation=1&theme=dark
What I want to do here is route back to '/' (BlockingForm component) from Topics component by clicking go root button.
How can I solve this?
import React, { useState } from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Prompt,
useParams,
useRouteMatch
} from "react-router-dom";
// Sometimes you want to prevent the user from
// navigating away from a page. The most common
// use case is when they have entered some data
// into a form but haven't submitted it yet, and
// you don't want them to lose it.
export default function PreventingTransitionsExample() {
return (
<Router>
<ul>
<li>
<Link to="/">Form</Link>
</li>
<li>
<Link to="/home">home</Link>
</li>
<li>
<Link to="/one">One</Link>
</li>
<li>
<Link to="/two">Two</Link>
</li>
</ul>
<Switch>
<Route path="/" exact children={<BlockingForm />} />
<Route path="/home" exact children={<NestingExample />} />
<Route path="/one" children={<h3>One</h3>} />
<Route path="/two" children={<h3>Two</h3>} />
</Switch>
</Router>
);
}
function NestingExample() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/home">Home</Link>
</li>
<li>
<Link to="home/topics">Topics</Link>
</li>
</ul>
<hr />
<Switch>
<Route exact path="/home/">
<Home />
</Route>
<Route path="/home/topics">
<Topics />
</Route>
</Switch>
</div>
</Router>
);
}
function Home() {
return (
<div>
<h2>Home</h2>
</div>
);
}
function Topics() {
// The `path` lets us build <Route> paths that are
// relative to the parent route, while the `url` lets
// us build relative links.
let { path, url } = useRouteMatch();
return (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${url}/rendering`}>Rendering with React</Link>
</li>
<li>
<Link to={`${url}/components`}>Components</Link>
</li>
<li>
<Link to={`${url}/props-v-state`}>Props v. State</Link>
</li>
<li>
<Link to={"/"}>Go root</Link>
</li>
</ul>
<Switch>
<Route exact path={path}>
<h3>Please select a topic.</h3>
</Route>
<Route path={`${path}/:topicId`}>
<Topic />
</Route>
</Switch>
</div>
);
}
function Topic() {
// The <Route> that rendered this component has a
// path of `/topics/:topicId`. The `:topicId` portion
// of the URL indicates a placeholder that we can
// get from `useParams()`.
let { topicId } = useParams();
return (
<div>
<h3>{topicId}</h3>
</div>
);
}
function BlockingForm() {
let [isBlocking, setIsBlocking] = useState(false);
return (
<form
onSubmit={(event) => {
event.preventDefault();
event.target.reset();
setIsBlocking(false);
}}
>
<Prompt
when={isBlocking}
message={(location) =>
`Are you sure you want to go to ${location.pathname}`
}
/>
<p>
Blocking? {isBlocking ? "Yes, click a link or the back button" : "Nope"}
</p>
<p>
<input
size="50"
placeholder="type something to block transitions"
onChange={(event) => {
setIsBlocking(event.target.value.length > 0);
}}
/>
</p>
<p>
<button>Submit to stop blocking</button>
</p>
</form>
);
}
You can achieve that by using the useHistory to manipulate the parent router (for react router v5)
reference:
https://v5.reactrouter.com/web/api/Hooks/usehistory
I've changed only the nested router in order to show you how to change the parent router location :)
import React, { useState } from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Prompt,
useParams,
useRouteMatch,
useHistory
} from "react-router-dom";
export default function PreventingTransitionsExample() {
return (
<Router>
<ul>
<li>
<Link to="/">Form</Link>
</li>
<li>
<Link to="/home">home</Link>
</li>
<li>
<Link to="/one">One</Link>
</li>
<li>
<Link to="/two">Two</Link>
</li>
</ul>
<Switch>
<Route path="/" exact children={<BlockingForm />} />
<Route path="/home" exact children={<NestingExample />} />
<Route path="/one" children={<h3>One</h3>} />
<Route path="/two" children={<h3>Two</h3>} />
</Switch>
</Router>
);
}
function NestingExample() {
//use useHistory hook to get the history context from parent router
const history = useHistory();
//use this function on a onClick event instead of <link> to change the parent router
const changeParentRouter = (url) => history.push(url);
return (
<Router>
<div>
<ul>
<li>
<Link to="/home">Home</Link>
</li>
<li>
<Link to="home/topics">Topics</Link>
</li>
</ul>
<hr />
<Switch>
<Route exact path="/home/">
<Home />
</Route>
<Route path="/home/topics">
<Topics />
</Route>
</Switch>
</div>
</Router>
);
}
For react router v6, use the useNavigate hook:
import { useNavigate } from "react-router-dom";
function useLogoutTimer() {
const userIsInactive = useFakeInactiveUser();
const navigate = useNavigate();
return <div>
<button onClick={() => navigateTo('/')}>
Dashboard
</button>
</div>
}
Reference: https://reactrouter.com/en/6.4.4/hooks/use-navigate

Rendering part of component only on few routes

I have my application and I'm using react-router.On every route I have rendered Navbar and in the Navbar I have input field which I want to render only on two routes. How can I do it ? Is there any "If" that can I use to render part of component when route is matched ?
In your Route you will have a collection of RouteParams (match, location and history). You can use e.g. the location.pathname to conditionally render whatever you want.
Something like:
<Router>
<Route render={({match,location,history})=>{
const showNavFields = location.pathname==='/your-path'
return {
<NavBar showNavFields={showNavFields}/>
}
}}/>
<Switch>
<Route path="/your-path" component="AComponent"/ >
<Route path="/your-other-path" component="AnotherComponent"/ >
</Switch>
</Router>
We can use match.url for that.
Working code. Here we are appending route matched to router url if route is /topics/rendering
import React from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
function BasicExample() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
</ul>
<hr />
<Route exact path="/" component={Home} />
<Route path="/topics" component={Topics} />
</div>
</Router>
);
}
function Home() {
return (
<div>
<h2>Home</h2>
</div>
);
}
function Topics({ match }) {
return (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${match.url}/rendering`}>Rendering with React</Link>
</li>
<li>
<Link to={`${match.url}/components`}>Components</Link>
</li>
</ul>
<Route path={`${match.path}/:topicId`} component={Topic} />
<Route
exact
path={match.path}
render={() => <h3>Please select a topic.</h3>}
/>
</div>
);
}
function Topic({ match }) {
if (match.url === '/topics/rendering') {
return (
<div>
<h3>route matched {match.params.topicId}</h3>
</div>
)
} else {
return (
<div>
<h3>{match.params.topicId}</h3>
</div>
)
}
}
export default BasicExample;

React router v4 - route with params renders blank page?

I'm trying to implement React Router v4 in my create-react-app app and my routes work except for the route with an /:id parameter as it just renders a blank page. I've searched for 2 days and tried implementing the solutions here which says to add <base href="/" /> to the head section of index.html and I've also tried following this Medium guide for simple routing but it still does not work. I don't understand, what is going on?
My code is below, any help is greatly appreciated!
Index.js
ReactDOM.render((
<BrowserRouter>
<App />
</BrowserRouter>),
document.getElementById('root'));
registerServiceWorker();
App.js
class App extends Component {
render() {
return (
<Routes />
)
}
}
Routes.js
export const Routes = () => {
return (
<main>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/movies" component={Search} />
</Switch>
</main>
);
}
Search.js
render() {
let filteredMovies = this.state.movies.filter((movie) => {
return movie.title.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1;
})
return (
<div>
<p>Search Page</p>
<form>
<input type="text" value={this.state.search} onChange={this.updateSearch}/>
</form>
<div>
{filteredMovies.map((movie, idx) =>
<div>
<div key={idx}>
<Link to={`/movies/${movie.videoId}`}>
<img src={movie.image.high.url} height="160px" width="100px" alt=""/>
<p>{movie.title}</p>
<p>{movie.quality}</p>
</Link>
</div>
</div>
)}
</div>
<Switch>
<Route path="/movies/:id" component={Single} />
</Switch>
</div>
)
}
}
export default Search;
Single.js
class Single extends Component {
render() {
return (
<div>
<p>Single Movie...</p>
{this.props.match.params.id}
</div>
)
}
}
export default withRouter(Single);
Every route works except for /movies/:id where it just renders a completely blank page. It doesn't even show the <p>Single Movie...</p>.
You should remove exact from route that corresponds to Search component in your Routes, i. e.:
export const Routes = () => {
return (
<main>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/movies" component={Search} />
</Switch>
</main>
);
}
The explanation is pretty simple: the exact prop means that your component will render only if current route is exactly the same as you specified in the path prop
Update
If you want to render the list of movies only when no :id is specified, you should render your list in Switch:
<Switch>
<Route exact path="/movies" render={props => {
return (
<div>
{filteredMovies.map((movie, idx) =>
<div>
<div key={idx}>
<Link to={`/movies/${movie.videoId}`}>
<img src={movie.image.high.url} height="160px" width="100px" alt=""/>
<p>{movie.title}</p>
<p>{movie.quality}</p>
</Link>
</div>
</div>
)}
</div>
)
} />
<Route path="/movies/:id" component={Single} />
</Switch>

Resources