Pass props to React component dependent on route - reactjs

I have a root component which uses react-router:
<HashRouter>
<aside>
<Sidebar />
</aside>
<main>
<Switch>
{/* -- routes go here -- */}
</Switch>
</main>
</HashRouter>
I want my Sidebar component to have different content depending on which route we're on. So if I have two routes, foo and bar, when I go to /foo I want Sidebar to have different props than when I visit /bar. I've tried passing the location as a prop:
<Sidebar location={this.context.router.location.pathname} />
But I'm pretty sure that's not how it works... And sure enough it didn't work.

You can use withRouter to pass values from route to another component. This way you can do conditional rendering or implement any other logic related to the current route to your component.
You can get access to the history object’s properties and the closest
's match via the withRouter higher-order component. withRouter
will re-render its component every time the route changes with the
same props as render props: { match, location, history }.
Example (from official documentation)
// A simple component that shows the pathname of the current location
class ShowTheLocation extends React.Component {
static propTypes = {
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
}
render() {
const { match, location, history } = this.props
return (
<div>You are now at {location.pathname}</div>
)
}
}
// Create a new component that is "connected" (to borrow redux
// terminology) to the router.
const ShowTheLocationWithRouter = withRouter(ShowTheLocation)

Here's a method using React Router's withRouter to 'connect' the <Sidebar /> component to the router.
With withRouter we create our <Sidebar /> component as normal, then 'connect' it like this:
//Sidebar
class Sidebar extends React.Component {
render() {
const { location } = this.props;
return (
<div>
<h1>Sidebar</h1>
<p>You are now at {this.props.location.pathname}</p>
</div>
)
}
}
const SidebarWithRouter = withRouter(Sidebar);
In the end we have a new <SidebarWithRouter /> component connected to the router so it has access to match, location, and history.
Unfortunately the code snippet won't work in Stackoverflow due to the history within the iframe, but here's the code, and a working Codepen.
let { BrowserRouter, Link, Route } = ReactRouterDOM;
let { Switch, withRouter } = ReactRouter;
let Router = BrowserRouter;
// App
class App extends React.Component {
render() {
return (
<Router>
<div className="container">
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
<hr />
<aside>
<SidebarWithRouter />
</aside>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
</div>
</Router>
);
}
}
//Home
const Home = () => (
<div>
<h1>Home</h1>
<p>This is the Home Page.</p>
</div>
);
//About
const About = () => (
<div>
<h1>About</h1>
<p>This is about</p>
</div>
);
//Sidebar
class Sidebar extends React.Component {
render() {
const { location } = this.props;
return (
<div>
<h1>Sidebar</h1>
<p>You are now at {this.props.location.pathname}</p>
</div>
)
}
}
const SidebarWithRouter = withRouter(Sidebar);
ReactDOM.render(<App />, document.getElementById("app"));

Related

Create a Default page without sidebar and route

Goal:
Show the first page (default page) that contain a button that goes to the page with sidebar link Home and About using Router.
Problem:
Today, you have a menu with link Home and About but if I want a default page (that is the main page that you enter) and then you go to another page that has sidebar and using route.
How should it be created?
Info:
*Newbie in Reactjs
*The main page (default) should not contain any sidebar or any route.
Stackblitz:
https://stackblitz.com/edit/react-k19hye?
import React from 'react';
import './style.css';
import React, { Component } from 'react';
import { render } from 'react-dom';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
const Nav = () => (
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
</div>
);
const HomePage = () => <h1>Home Page</h1>;
const AboutPage = () => <h1>About Page</h1>;
export default class App extends Component {
constructor() {
super();
this.state = {
name: 'React',
};
}
render() {
return (
<Router>
{/* Router component can have only 1 child. We'll use a simple
div element for this example. */}
<div>
<Nav />
<Route exact path="/" component={HomePage} />
<Route path="/about" component={AboutPage} />
</div>
</Router>
);
}
}
render(<App />, document.getElementById('root'));
In order to set another default page, you should first update the route to HomePage to /home. And then define another route for the DefaultPage like <Route exact path="/" component={DefaultPage} />. In order to hide sidebar on the DefaultPage, you can use Switch to show only DefaultPage on route /.
You can take a look at this updated stackblitz forked from your original example for a live working example. Here is the final full code of this usage:
import React from 'react';
import './style.css';
import React, { Component } from 'react';
import { render } from 'react-dom';
import {
BrowserRouter as Router,
Route,
Link,
Switch,
useHistory,
} from 'react-router-dom';
const Nav = () => (
<div>
<ul>
<li>
<Link to="/home">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
</div>
);
const DefaultPage = () => {
const history = useHistory();
return (
<div>
<h1>Default Page</h1>
<button onClick={() => history.push('/main')}>go to main</button>
</div>
);
};
const HomePage = () => <h1>Home Page</h1>;
const AboutPage = () => <h1>About Page</h1>;
export default class App extends Component {
constructor() {
super();
this.state = {
name: 'React',
};
}
render() {
return (
<Router>
<Switch>
{/* Router component can have only 1 child. We'll use a simple
div element for this example. */}
<Route exact path="/" component={DefaultPage} />
<div>
<Nav />
<Route exact path="/home" component={HomePage} />
<Route exact path="/about" component={AboutPage} />
</div>
</Switch>
</Router>
);
}
}
render(<App />, document.getElementById('root'));

React-router URL changes but page is still unchanged

I am new to react and react-router, so please go easy on me.
I am trying to implement router in my Todo List project, where path="/" takes me to my todo list and path="/id" takes me to a test page (later will show the description of the task).
When I click the link that takes me to "/id", the URL in the browser changes but the page/content doesn't. However, when I refresh my browser, the test page loads.
I have put the Switch in App.js shown below.
import React, { Component } from "react";
import "./App.css";
import TodoList from "./components/TodoList";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import Test from "./components/Test";
class App extends Component {
render() {
return (
<Router>
<div className="todo-app">
<p>
<Link to="/">Home</Link>
</p>
<Switch>
<Route exact path="/" component={TodoList} />
<Route path={`/id`} component={Test} />
</Switch>
</div>
</Router>
);
}
}
export default App;
And I have put the Link to "/id" as shown below in a child component of component which is called here in App.js.
<div key={todo.id}>
<Link className="todo-text" to={`/id/${todo.id}`}>
{todo.text}
</Link>
</div>
Am I missing something which is causing my component to not load when I click the link?
Edit: Here's a link to my project. https://stackblitz.com/edit/react-7cpjp9?file=src/index.js
Issue
Ok, the issue is exactly as I had suspected. You are rendering multiple routers in your app. The first is a BrowserRouter in your index.js file, the second, another BrowserRouter in App.js, and at least a third BrowserRouter in Todo.js. You need only one router to provide a routing context for the entire app.
The issue here is that the router in Todo component is the closest router context to the links to specific todo details. When a link in Todo is clicked, this closest router handles the navigation request and updates the URL in the address bar. The blocks, or "masks", the router in App component or index.js that is rendering the routes from "seeing" that a navigation action occurred. In other words, the URL in the address bar is updated by the inner router, but the outer router doesn't know to render a different route.
Solution
Keep the BrowserRouter wrapping App in index.js and remove all other routers used in your app.
App - Remove the Router component. Also, reorder the routes/paths from most specific to least specific so you don't need to specify the exact prop on every route. Allows more specific paths to be matched and rendered before less specific paths by the Switch component.
class App extends Component {
render() {
return (
<div className="todo-app">
<p>
<Link to="/">Home</Link>
</p>
<Switch>
<Route path="/id/:todoId" component={Test} />
<Route path="/" component={TodoList} />
</Switch>
</div>
);
}
}
Todo - Remove the Router component. Move the key={todo.id} up to the outer-most element so when todos array is updated React can reconcile updates.
class Todo extends Component {
constructor(props) {
super(props);
this.state = {
id: null,
value: "",
details: "",
};
this.submitUpdate = this.submitUpdate.bind(this);
}
submitUpdate(value) {
const { updateTodo } = this.props;
updateTodo(this.state.id, value);
this.setState({
id: null,
value: "",
});
}
render() {
const { todos, completeTodo, removeTodo } = this.props;
if (this.state.id) {
return <TodoForm edit={this.state} onSubmit={this.submitUpdate} />;
}
return todos.map((todo, index) => (
<div
className={todo.isComplete ? "todo-row complete" : "todo-row"}
key={todo.id}
>
<div>
<Link className="todo-text" to={`/id/${todo.id}`}>
{todo.text}
</Link>
</div>
<div className="icons">
<RiCloseCircleLine
onClick={() => removeTodo(todo.id)}
className="delete-icon"
/>
<TiEdit
onClick={() => this.setState({ id: todo.id, value: todo.text })}
className="edit-icon"
/>
<RiCheckboxCircleLine
onClick={() => completeTodo(todo.id)}
className="delete-icon"
/>
</div>
</div>
));
}
}
First of all the approach, you are taking for dynamic routing is wrong.
It should be like this you will have to add the exact keyword on the dynamic route.
<Route exact path="/id/:todoId" component={Test} />
And
<div key={todo.id}>
<Link className="todo-text" to={`/id/${todo.id}`}>
{todo.text}
</Link>
import React, { Component } from "react";
import "./App.css";
import TodoList from "./components/TodoList";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import Test from "./components/Test";
class App extends Component {
render() {
return (
<Router>
<div className="todo-app">
<p>
<Link to="/">Home</Link>
</p>
<Switch>
<Route exact path="/" component={TodoList} />
**<Route exact path={`/id`} component={Test} />**
</Switch>
</div>
</Router>
);
}
}
export default App;

Binding state to Route Switch

I am building a React app that fetches JSON data from an API call and feeds the response into an app state called menus [] as follows:
App.js
import React,{Component} from 'react';
import Menus from './components/menus';
class App extends Component {
state = {
menus: []
}
componentDidMount(){
fetch('api url')
.then(res => res.json())
.then((data)=> {
this.setState({menus: data})
})
.catch(console.log)
}
render(){
return (
<div>
<Menus menus={this.state.menus} />
</div>
);
}
}
export default App;
Im using the state in a simple component that contains a route switch as follows:
Menus.js
import React from 'react';
import {BrowserRouter as Router, Switch, Route, Link, useParams } from "react-router-dom";
const Menus = ({ menus }) => {
return (
<Router>
<div>
<center><h1>Lessons</h1></center>
{menus.map((menu) => (
<li><Link to="{{menu.lessonContent}}">{menu.lessonName}</Link></li>
))}
<Switch>
<Route path="/:id" children={<Child />} />
</Switch>
</div>
</Router>
)
};
function Child() {
// We can use the `useParams` hook here to access
// the dynamic pieces of the URL.
let { id } = useParams();
return (
<div>
<h3>ID: {id}</h3> //content should show up here
</div>
);
}
export default Menus
Is there a way to bind a state element to the route so a different portion of the json response will be passed instead of a route? Can I use a key and somehow combine it with an element?
Here is what the link was originally:
<li><Link to="/route">{menu.lessonName}</Link></li>
and here is what I need it to be:
<li><Link to="{{menu.lessonContent}}">{menu.lessonName}</Link></li>
If I understand correctly what you are trying achieve, I think your Menus component should look more like this:
Menus.js
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useParams
} from "react-router-dom";
const Menus = ({ menus }) => {
return (
<Router>
<div>
<center>
<h1>Lessons</h1>
</center>
{menus.map(menu => (
<li key={menu.id}>
<Link to={`/${menu.id}`}>{menu.lessonName}</Link>
</li>
))}
<Switch>
{menus.map(menu => (
<Route
key={menu.id}
path="/:id"
children={<Child content={menu.content} />}
/>
))}
</Switch>
</div>
</Router>
);
};
function Child({ content }) {
let { id } = useParams();
return (
<div>
<h3>ID: {id}</h3>
<div>{content}</div>
</div>
);
}
export default Menus;
Here are some of the things I fixed here:
On your example you were passing a string instead of an object key to the to prop: to="{{menu.lessonContent}}" instead of to={menu.lessonContent}
I'm mapping over menus in order to dynamically create the Route components and their corresponding paths.
I'm passing menu.content as a prop to the Child component.
I added keys to every rendered element which is a result of iteration.

I am trying to go back to the previous page on click of a button in react

import {BrowserRouter as Router, Route} from 'react-router-dom';
import Home from './Home';
class App extends Component {
constructor(props){
super(props);
this.state = {value: true}
this.goBack = this.goBack.bind(this);
}
goBack() {
this.props.history.goBack();
}
render() {
return (
<Router>
<div className="App">
<div className="App-header">
<button onClick={this.goBack}>Go Back</button>
</div>
<Route path="/" exact render={() => <Home value={this.state.value}/>}/>
<Route path="/details/:id" component={DetailView}/>
</div>
</Router>
);
}
}
export default App;
This is code. On click of Back button i want to take me to the previous age. But this goBack() is not working for me. Probably I am making some mistake in using it.I tried couple of ways from guthub and stackover flow but nothing worked.
can you try adding withRouter
import {..., withRouter} from 'react-router-dom';
then change export to
export default wihRouter(App);
App component does not receive history as prop because the Router is rendered inside it, instead you can create a wrapper component that is in the route to use this.props.history.
class App extends Component {
render() {
return (
<Router>
<Route path="/" component={Content} />
</Router>
)
}
}
For the content component:
class Content extends Component {
constructor(props){
super(props);
this.state = {value: true}
this.goBack = this.goBack.bind(this);
}
goBack() {
this.props.history.goBack();
}
render() {
return (
<div className="App">
<div className="App-header">
<button onClick={this.goBack}>Go Back</button>
</div>
<Route path="/" exact render={() => <Home value={this.state.value}/>}/>
<Route path="/details/:id" component={DetailView}/>
</div>
);
}
}
Now, Content component is in the route and can receive the history prop.
Another way you can handle this is to simply render the Content component in App with <Content /> and then use withRouter HOC on Content.
withRouter
PS: You cannot apply withRouter to App component because technically App is outside the Router

how to pass props one page to another page via react router?

In my React app I'm using react-router-dom. In App.js I have set my routes. There I have three components: /home, /customerinfo and /success.
In the home component I have a button. What I want is that when I press the button, the customerinfo component will load in full page and I want the state of the home component in the customerinfo component. This is what my Route looks like:
<Route
path="/customerInfo"
render={props => <CustomerInfo {...props} />}
/>
But I don't have access of the state of the home component in App.js, so it's not working.
How can I get the state of the home component in customerinfo?
I'm new in React. Please help me.
Use redux to pass data for complex application.
But if you want to stick around using react only then you could pass data using props to Redirect Component.
When CustomerInfo button is clicked data from home Controller is passed down to customerInfo component.
import React from "react";
import { BrowserRouter as Router, Route, Link, Redirect } from "react-router-dom";
function BasicExample() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
</ul>
<hr />
<Route exact path="/" component={Home} />
<Route path="/customerinfo" component={CustomerInfo} />
</div>
</Router>
);
}
class Home extends React.Component {
state = {
redirect: false,
data: 'data passed through home route to customer info route'
};
handleClick = () => {
this.setState({
redirect: true
})
}
render () {
if (this.state.redirect) {
return <Redirect to={{
pathname: '/customerinfo',
state: { data: this.state.data }
}} />
} else {
return (
<div>
<h2>Home</h2>
<button onClick={this.handleClick}>CustomerInfo</button>
</div>
);
}
}
}
function CustomerInfo({ location }) {
console.log(location)
return (
<div>
<h2>{location.state.data}</h2>
</div>
);
}
export default BasicExample;
Hope that helps!!!

Resources