Binding state to Route Switch - reactjs

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.

Related

How to Return a Page by ID with react-routing?

I'm just new to React and I'm having a hard time figuring out how to return a new page (a component) as I clicked the View button. I have a user table and per row has a View button. I can only retrieve the data in exactly the same page but I would like to retrieve my view details in another page with its id on the url, for example: http://user-details-1. Can someone help me with this. Thanks!
Here's my view code/component:
import React, { useState, useEffect } from "react";
import Form from "react-bootstrap/Form";
const ViewUserDetaiils = (props) => {
const [user, setUser] = useState(props.currentUser);
useEffect(() => {
setUser(props.currentUser);
}, [props]);
return (
<Form>
<div>
<div>
<strong>Id:</strong> {user.id}{" "}
</div>
<div>
<strong>Name:</strong> {user.name}{" "}
</div>
<div>
<strong>Contact:</strong> {user.contact}{" "}
</div>
<div>
<strong>Email:</strong> {user.email}{" "}
</div>
</div>
</Form>
);
};
export default ViewUserDetails;
Here's my routing:
import React, { Component } from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import Home from "./containers/Home/Home";
import ViewUserDetails from "./forms/ViewUserDetails";
import PageNotFound from "./page404/page404";
class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/home" component={Home} />
<Route
exact
path="/view-contact-details/"
component={ViewUserDetails}
/>
<Route component={PageNotFound} />
</Switch>
</div>
</BrowserRouter>
);
}
}
export default App;
And here's my code for the View button which is in another component(UserTable component).:
<Link to="/view-contact-details">
<Button
onClick={() => {
props.viewRow(user);
}}
className="Button muted-Button"
>
View
</Button>
</Link>
Your route must include a slug (the last part of the URL, in this case the user id) to route it dynamically.
<Route
exact
path="/view-contact-details/:id"
component={ViewUserDetails}
/>
Then in your component's Link you pass an object to attribute to. It accepts a pathname (the path with slug/id included) and state (this contains your state/data).
<Link
to={{
pathname: `/view-contact-details/${user.id}`,
state: { users: user }
}}
>
<button>View</button>
</Link>;
Finally, in your ViewUserDetails component, you can use useLocation to get the state passed in Link.
import React from "react";
import { useLocation, Link } from "react-router-dom";
import Form from "react-bootstrap/Form";
const ViewUserDetails = _ => {
const { state } = useLocation();
return (
<Form>
<div>
<div>
<strong>Id:</strong> {state.users.id}{" "}
</div>
<div>
<strong>Name:</strong> {state.users.name}{" "}
</div>
</div>
</Form>
);
};
export default ViewUserDetails;
Here is a working demo in case you want to check it out.
For those who are facing issues in react-router-dom library v6 (state was displaying null in my case), they can refer this link: here

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!!!

Routing in React JS on click

I am new to React and I want to navigate to another component on button click. I just want to perform a simple routing. This is the code that I tried. But I am not able to route it.
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom'
import Hello from './HelloComponent';
class App extends Component {
constructor(props) {
super(props);
this.try = this.try.bind(this)
}
try = () => {
alert();
<div>
<Router>
<Route path="/hello" component={Hello} />
</Router>
</div>
}
render() {
return (
<div className="App">
<div className="container">
<button id="b1" onClick={this.try}>Click me</button>
</div>
</div>
);
}
}
export default App;
Please help me with this code to perform basic routing in react JS
You cannot return JSX to onClick handler since it won't do anything with it.
You need to configure your Routes in render in advance and use history.push to change the Route
Below is a sample code that you can refer
import React, { Component } from 'react';
import { BrowserRouter as Router, Route,Switch, Redirect} from 'react-router-dom'
import Hello from './HelloComponent';
class App extends Component {
try = () => {
this.props.history.push('/hello');
}
render() {
return (
<div className="App">
<div className="container">
<button id="b1" onClick ={this.try}>Click me</button>
<Route path="/hello" component={Hello}/>
</div>
</div>
);
}
}
export default () => (
<div>
<Router>
<Route component={App} />
</Router>
</div>
);
I recommend you look at the doc.
<Route path="/hello" component={Hello}/> will display the component Hello exactly where the <Route/> is, but I think your function will do nothing here as it returns a <div> but where does it go?
You need some sort of "higher" component that will render your routes, then call a <Link/>
Then try nesting the button inside the <Link/> ?
<Link to="/??">
<button id="b1">
Click me
</button>
</Link>
in your code
try = () => {
alert();
<div>
<Router>
<Route path="/hello" component={Hello}/>
</Router>
</div>
}
your just pushing the route and it's not a action to take you to different page
bellow code will work fine and it's good practice to place router in separate component. click here you can find this code in codesandbox
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import "./styles.css";
function RouterComponet() {
return (
<Router>
<Switch>
<Route exact path="/" component={App} />
<Route path="/user" component={User} />
</Switch>
</Router>
);
}
class App extends Component {
constructor(props) {
super(props);
}
onClick = () => {
this.props.history.push("/user");
};
render() {
return (
<div>
<h1>App component</h1>
<a onClick={this.onClick} className="link">
click here
</a>{" "}
to user page
</div>
);
}
}
class User extends Component {
constructor(props) {
super(props);
}
onClick = () => {
this.props.history.push("/");
};
render() {
return (
<div>
<h1>User Componet</h1>
<a onClick={this.onClick} className="link">
click here
</a>{" "}
to back
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<RouterComponet />, rootElement);
I have created a demo that brings it all together. It has three files App.js, About.js, Contacts.js. To Navigate to any component, you need to add its route in App.js, Then depending on the location of your button (About.js), wrap it with Link that helps the element navigate to the specified route. When clicked, the Contacts component should be loaded. Hope this helps. code demo
App.js
import React from "react";
import { Switch, Route, BrowserRouter } from "react-router-dom";
import About from "./About";
import Contact from "./Contacts";
function App() {
return (
<BrowserRouter>
<Switch>
<Route path="/" component={About} exact />
<Route path="/contacts" component={Contact} />
</Switch>
</BrowserRouter>
);
}
export default App;
About.js
import React from "react";
import { Link } from "react-router-dom";
function About() {
return (
<div>
<p>
Lorem Ipsum is simply dummy text of the printing and typesetting
industry.
</p>
<Link to="/contacts">
<button>click me</button>
</Link>
</div>
);
}
export default About;
Contacts.js
import React from "react";
function Contact() {
return <div>Call me!!</div>;
}
export default Contact;
This is the first SO post on google, so I'd like answer the question with updated coding style and answer:
From react v6 you use the useNavigation hook. (Reference)
import { useNavigate } from 'react-router-dom';
export const MyComponent = () => {
const navigate = useNavigate();
return (
<>
<button
onClick={() => {
navigate('/');
}}
>
click me
</button>
</>
);
};

How to click a button and navigate to a new route with URL params using React Router 4

I'm building a simple app in Meteor with React, and I'm using React Router 4.
There is a button which creates a new map, and when the server returns the new map's id, the browser should navigate to the page that shows the newly created map.
I have got it nearly working but when I click the button, the URL changes in the address bar but the map component doesn't render. Refreshing the page doesn't help. But it works fine if I click the new map's link in the list of maps.
This code is ugly and I'm sure it's not the best way to do any of it, but it is literally the only way I've been able to make it work at all. I've read all the forum posts and docs I can find but nothing is clear to me.
I'd be very grateful for any help.
Main.js
```JSX
/* global document */
import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
// Start the app with imports
import '/imports/startup/client';
import '../imports/startup/accounts-config.js';
import App from '../imports/ui/layouts/App.jsx';
Meteor.startup(() => {
render((<BrowserRouter><App /></BrowserRouter>), document.getElementById('app'));
});
App.js:
import React, { Component } from 'react';
import { withRouter, Switch, Route, Link, Redirect } from 'react-router-dom';
import { Meteor } from 'meteor/meteor';
import { withTracker } from 'meteor/react-meteor-data';
// Mongodb collection
import { Maps } from '../../api/maps/maps.js';
// User Accounts
import AccountsUIWrapper from '../AccountsUIWrapper.jsx';
import Home from '../pages/home';
import Map from '../pages/map';
import About from '../pages/about';
class App extends Component {
renderTestButton() {
return (
<button onClick={this.handleClick.bind(this)}>New Map</button>
);
}
handleClick() {
let that = this;
Meteor.call('newMap', {'name': 'new map'}, function(error, result) {
that.props.history.push(`/map/${result}`);
});
}
render() {
let newMap = this.renderNewMap();
let testButton = this.renderTestButton();
return (
<div className="primary-layout">
<header>
<AccountsUIWrapper />
{testButton}
<nav>
<ul>
<li><Link to='/'>Home</Link></li>
<li><Link to='/about'>About</Link></li>
</ul>
</nav>
</header>
<main>
<Switch>
<Route exact path='/' component={Home}/>
<Route exact path="/map/:_id" render={({ match }) => (
<Map
params={match.params}
/>
)} />
<Route path='/about' component={About}/>
</Switch>
</main>
</div>
);
}
}
export default withRouter(withTracker(() => {
const mapsHandle = Meteor.subscribe('maps');
return {
'maps': Maps.find({}).fetch(),
'loading': !mapsHandle.ready(),
'currentUser': Meteor.user(),
};
})(App));
EDIT: I had a typo in my path, I wrote that.props.history.push(/maps/${result}); when it should be that.props.history.push(/map/${result}); to match the defined route.
I've correct the code, it now works but I still feel this can't be the best solution...
After finding a typo in my original code ('/maps/' where the path should have been '/map/') I found another 'gotcha' which is this:
If the route expects a URL parameter, and it is not supplied, then the route doesn't seem to render at all. My route is defined as:
```JSX
<Route path="/map/:_id" render={({ match }) => (
<Map
params={match.params}
/>
)} />
If you try to navigate to 'http://localhost:3000/map/' then the component doesn't render. If you put any value on the end e.g. 'http://localhost:3000/map/dummyvalue' it renders.
I've now got a tidier version working:
```JSX
import React, { Component } from 'react';
import { withRouter, Switch, Route, Link } from 'react-router-dom';
import { Meteor } from 'meteor/meteor';
import { withTracker } from 'meteor/react-meteor-data';
// Mongodb collection
import { Maps } from '../../api/maps/maps.js';
// User Accounts
import AccountsUIWrapper from '../AccountsUIWrapper.jsx';
import Home from '../pages/home';
import Map from '../pages/map';
import About from '../pages/about';
function NewMap(props) {
function handleClick(e) {
e.preventDefault();
let history = props.history;
Meteor.call('newMap', {'name': 'new map'}, (error, result) => {
history.push(`/map/${result}`);
});
}
let disabled = 'disabled';
if (Meteor.userId()) { disabled = '';}
return (
<button disabled={disabled} onClick={handleClick}>New Map</button>
);
}
class App extends Component {
renderNewMap() {
const history = this.props.history;
return (
<NewMap history={history}
/>
);
}
render() {
let newMap = this.renderNewMap();
return (
<div className="primary-layout">
<header>
<AccountsUIWrapper />
{newMap}
<nav>
<ul>
<li><Link to='/'>Home</Link></li>
<li><Link to='/about'>About</Link></li>
</ul>
</nav>
</header>
<main>
<Switch>
<Route exact path='/' component={Home}/>
<Route path="/map/:_id" render={({ match }) => (
<Map
params={match.params}
/>
)} />
<Route path='/about' component={About}/>
</Switch>
</main>
</div>
);
}
}
export default withRouter(withTracker(() => {
const mapsHandle = Meteor.subscribe('maps');
return {
'maps': Maps.find({}).fetch(),
'loading': !mapsHandle.ready(),
'currentUser': Meteor.user(),
};
})(App));

Pass props to React component dependent on route

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"));

Resources