I can get the data of the players when i Route to the Players Component,
but when i click on the Link Tags, the PLayersContainer Component is not opening.
This is my App.js File.
import React, { Component } from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import Players from './Components/Players'
import PlayersContainer from './Components/Container/playersContainer'
import Navigation from './Components/Navigation';
export default class App extends Component {
state = {
players:[
{
id:1,
name:'Ronaldo'
},
{
id:2,
name:'Messi'
}
]
}
render() {
return (
<Router>
<Navigation />
<Switch>
<Route path="/players" render={(props) => <Players {...props} players={this.state.players} />} />
<Route exact path="/players/:id" render={PlayersContainer} />
</Switch>
</Router>
)
}
}
This is my Players Component.
import React from 'react'
import { Link } from 'react-router-dom'
export default function Players(props) {
const renderPlayers = () => {
let players = props.players.map(playerObj => <li> <Link to={`/players/${playerObj.id}`}> Player {playerObj.name} </Link></li>)
return players
}
return (
<div>
<ul>
{renderPlayers()}
</ul>
</div>
)
}
This is my PlayersContainer Component, where i want to render the individual data of the Player.
import React from 'react'
import { Link } from 'react-router-dom'
export default function PlayersContainer(props) {
const renderPlayers = () => {
console.log(props);
}
return (
<div>
<ul>
{renderPlayers()}
</ul>
</div>
)
}
You have the wrong Route marked as exact. The way its written currently, anything beginning with /players will match the first route. Since its in a switch, only the first match will be rendered.
Change it from:
<Route path="/players" render={(props) => <Players {...props} players={this.state.players} />} />
<Route exact path="/players/:id" render={PlayersContainer} />
to this:
<Route exact path="/players" render={(props) => <Players {...props} players={this.state.players} />} />
<Route path="/players/:id" render={PlayersContainer} />
Now only exactly /players will match the first route, and /players/id can continue past it to match the second.
Related
here I am building a website and I am using react-router-dom
everything seems to work fine when I use the Navbar and Footer components in every page.
So I come up with idea to wrap the component in a wrapper which contains Navbar and Footer.
When I click on Link or maybe NavLink, it seems to work fine but when I click again on same or another link in navbar then it navigates to a sub path under the previous selected path.
like this
on Single Click:
http://localhost:3000/projects
on clicking the same link again:
http://localhost:3000/projects/projects
App.js
import './App.css';
import {BrowserRouter as Router, Route, Outlet, Routes} from 'react-router-dom'
import {CommonScreen, Wrapper, About, Email, Projects} from './Barell'
function App() {
return (
<>
<Router>
<Routes>
<Route index element={<Wrapper children={<CommonScreen/>}/>}/>
<Route path='about' element={<Wrapper children={<About/>}/>}/>
<Route path='projects' element={<Wrapper children={<Projects/>}/>}/>
<Route path='email' element={<Email/>}/>
</Routes>
</Router>
<Outlet/>
</>
);
}
export default App;
Navbar.jsx:
import React from 'react'
import '../../index.css'
import { NavLink } from 'react-router-dom'
const links = [
{
name:"Home",
slug:"/",
},
{
name:"Projects",
slug:"projects",
},
{
name:"About",
slug:"about",
},
{
name:"Contact",
slug:"email",
},
]
export const Navbar = () => {
let activeStyle = {
textDecoration: "underline",
};
return (
<>
<nav>
<div id='brand'>Prema<span>culture</span></div>
<ul id='links'>
{
links.map((current,index) => (
<li>
<NavLink
key={index}
to={current.slug}
style={({ isActive }) =>
isActive ? activeStyle : undefined
}
>
{current.name}
</NavLink>
</li>
))
}
</ul>
</nav>
</>
)
}
Wrapper.jsx:
import React from 'react'
import { Navbar, Footer } from '../Barell'
export const Wrapper = ({children}) => {
return (
<>
<Navbar/>
{children}
<Footer/>
</>
)
}
You are using relative paths (versus absolute), so when already on a route, say "/projects", and then click a link that navigates to "about", the code links relative from "/projects" and results in "/projects/about".
To resolve you can make all the link paths absolute by prefixing a leading "/" character. Absolute paths begin with "/".
Example:
const links = [
{
name:"Home",
slug:"/",
},
{
name:"Projects",
slug:"/projects",
},
{
name:"About",
slug:"/about",
},
{
name:"Contact",
slug:"/email",
},
];
Additionally, to make the code more DRY you might also want to convert the Wrapper component into a layout route. Layout routes render an Outlet component for nested routes to be rendered into instead of a children prop for a single routes content.
Example
import React from 'react';
import { Outlet } from 'react-router-dom';
import { Navbar, Footer } from '../Barell';
export const Layout = () => {
return (
<>
<Navbar/>
<Outlet />
<Footer/>
</>
)
}
...
function App() {
return (
<>
<Router>
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<CommonScreen />} />
<Route path='about' element={<About />} />
<Route path='projects' element={<Projects />} />
</Route>
<Route path='email' element={<Email />} />
</Routes>
</Router>
<Outlet />
</>
);
}
I'm trying to create triple nesting with react-router but it's not working.
Here is the expected output
I want to perform but I am trying to implement this with the router so my tab should get switch according to URL passed to it.
When the user just write / in the URL it should redirect to login page (working)
When the user write /dashboard it should redirect to the dashboard with side list (working)
When the user click on list items such as about in drawer of the dashboard then it should render about page.
Now my about page contains two tabs one for general and one for account (Everything was working until I implement this feature now the whole app has been crashed and not even showing the error)
Here is my code in sandbox. Note: I have commented on my third nested route because it was crashing entire app
App.js
import { Switch, Route } from "react-router-dom";
import Login from "./pages/Login";
import Dashboard from "./pages/Dashboard";
import "./styles.css";
export default function App() {
return (
<div className="App">
<Switch>
<Route path="/dashboard" component={Dashboard} />
<Route path="/" exact component={Login} />
</Switch>
</div>
);
}
Dashboard.js for nested routing
import { Switch, Redirect, Route, useRouteMatch } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import AppDrawerBar from "../components/AppDrawerBar";
const Dashboard = () => {
const { path } = useRouteMatch();
return (
<>
<AppDrawerBar>
<Switch>
<Route path={`${path}/about`} component={About} />
<Route path={`${path}/home`} component={Home} />
<Redirect to={`${path}/home`} />
</Switch>
</AppDrawerBar>
</>
);
};
export default Dashboard;
About.js third nested routing to handle tab navigation after clicking on about list item
import { Switch } from "#material-ui/core";
import React from "react";
import { Redirect, Route } from "react-router-dom";
import TabMenu from "../components/TabMenu";
const About = (props) => {
return (
<>
<h1>Welcome to about</h1>
{/* This doesn't work.. this code is to render tab navigation */}
{/* <Switch>
<Redirect exact from={`${props.path}/about`} to="about/general" />
<Route
exact
path="/about/:page?"
render={(props) => <TabMenu {...props} />}
/>
</Switch> */}
</>
);
};
export default About;
TabMenu.js to handle my tabs
import { Paper, Tab, Tabs } from "#material-ui/core";
import React, { useState } from "react";
import General from "../pages/General";
import Account from "../pages/Account";
const TabMenu = (props) => {
const { match, history } = props;
const { params } = match;
const { page } = params;
const tabNameToIndex = {
0: "about",
1: "contact"
};
const indexToTabName = {
about: 0,
contact: 1
};
const [selectedTab, setSelectedTab] = useState(indexToTabName[page]);
const handleChange = (event, newValue) => {
history.push(`/about/${tabNameToIndex[newValue]}`);
setSelectedTab(newValue);
};
return (
<>
<Paper elevation={2}>
<Tabs
value={selectedTab}
onChange={handleChange}
indicatorColor="primary"
textColor="primary"
>
<Tab label="General" />
<Tab label="Profile" />
</Tabs>
</Paper>
{selectedTab === 0 && <General />}
{selectedTab === 1 && <Account />}
</>
);
};
export default TabMenu;
I have some problem with react-router. It doesn't go to Edit page when I click on id. The id is in URL, but it doesn't do anything.
const Main = (props) => {
const { pathname } = props.location;
return (
<Fragment>
<div>
<div className="container">
<Header />
{pathname === "/create" ? <Create /> : null}
{pathname === '/edit/:id' ? <Edit /> : null}
{pathname === "/" ? <Home /> : null}
</div>
</div>
</Fragment>
);
};
export default withRouter(Main);
app.js:
require('./components/Index');
import React from 'react'
import ReactDOM from "react-dom"
import Index from "./components/Index";
import { BrowserRouter as Router} from "react-router-dom";
import { ToastContainer } from 'react-toastify';
const App =() =>{
}
if (document.getElementById('app')) {
ReactDOM.render(<Router> <Index /> <ToastContainer /></Router>, document.getElementById('app'));
}
index.js:
import React from "react";
import { Route, Switch } from "react-router-dom";
import Main from "./CRUD/Main";
import Create from "./CRUD/Create";
import Edit from "./CRUD/Edit";
import Home from "./CRUD/Home";
const Index = (props) => {
return (
<Main>
<Switch>
<Route path="/Create" component={Create} />
<Route path='/edit/:id' component={Edit} />
<Route exact path="/" component={Home} />
</Switch>
</Main>
);
};
export default Index;
I think main.js have some problems with pathname.
You don't need to do conditional rendering like you are doing in Main when using react-router. Switch will automatically render the first child <Route> or <Redirect> that will match the location.
Hence, you need to remove Main from your router component i.e. Index so that it looks like as shown below:
const Index = (props) => {
return (
<>
<NavBar /> {/* NavBar is optional, I just added for example */}
<Switch>
<Route path="/create" component={Create} />
<Route path="/edit/:id" component={Edit} />
<Route exact path="/" component={Home} />
</Switch>
</>
);
}
NavBar: (Links; just for example)
function NavBar() {
return (
<ul>
<li>
<Link to="/create">Go to create</Link>
</li>
<li>
<Link to="/edit/123">Go to edit with id = 123</Link>
</li>
<li>
<Link to="/">Go to home</Link>
</li>
</ul>
);
}
Now, when you click on the above links, it will automatically take you to the related component (as declared in routes i.e. Index). (no manually condition checking)
And example, to retrieve the URL param i.e. id in Edit component using useParams hook:
function Edit() {
const { id } = useParams<{ id: string }>(); // Remove <{ id: string }> if not using TypeScript
return <h2>Edit, {id}</h2>;
}
Trying to make certain routes require Authentication.
I have this:
class App extends Component {
render() {
const menuClass = `${this.props.contentMenuClass} col-xs-12 col-md-9`;
return (
<BrowserRouter history={browserHistory}>
<div className="App">
<Header properties={this.props} />
<div className="container-fluid">
<div className="row">
<SideNav />
<div className={menuClass} id="mainContent">
<Switch>
{routes.map(prop =>
(
<Route
path={prop.path}
component={prop.component}
key={prop.id}
render={() => (
!AuthenticationService.IsAutheenticated() ?
<Redirect to="/Login"/>
:
<Route path={prop.path}
component={prop.component}
key={prop.id}/>
)}
/>
))}
</Switch>
</div>
</div>
</div>
{/* <Footer /> */}
</div>
</BrowserRouter>
);
}
}
const mapStateToProps = state => ({
contentMenuClass: state.menu,
});
export default connect(mapStateToProps)(App);
Note: Yes the auth service works as it should.
For every route I am checking if the user is authenticated, if not then I want to redirect them to the login page, if they are then it will land on the first page with the route of "/".
All I am getting is:
react-dom.development.js:14227 The above error occurred in the <Route> component:
in Route (created by App)
in Switch (created by App)
in div (created by App)
in div (created by App)
in div (created by App)
in div (created by App)
in Router (created by BrowserRouter)
in BrowserRouter (created by App)
in App (created by Connect(App))
in Connect(App)
in Provider
Where am I doing this wrong?
A simple solution would be to make a HOC (High Order Component) that wraps all protected routes.
Depending upon how nested your app is, you may want to utilize local state or redux state.
Working example: https://codesandbox.io/s/5m2690nn6n (this uses local state)
routes/index.js
import React from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import Home from "../components/Home";
import Players from "../components/Players";
import Schedule from "../components/Schedule";
import RequireAuth from "../components/RequireAuth";
export default () => (
<BrowserRouter>
<RequireAuth>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/players" component={Players} />
<Route path="/schedule" component={Schedule} />
</Switch>
</RequireAuth>
</BrowserRouter>
);
components/RequireAuth.js
import React, { Component, Fragment } from "react";
import { withRouter } from "react-router-dom";
import Login from "./Login";
import Header from "./Header";
class RequireAuth extends Component {
state = { isAuthenticated: false };
componentDidMount = () => {
if (!this.state.isAuthenticated) {
this.props.history.push("/");
}
};
componentDidUpdate = (prevProps, prevState) => {
if (
this.props.location.pathname !== prevProps.location.pathname &&
!this.state.isAuthenticated
) {
this.props.history.push("/");
}
};
isAuthed = () => this.setState({ isAuthenticated: true });
unAuth = () => this.setState({ isAuthenticated: false });
render = () =>
!this.state.isAuthenticated ? (
<Login isAuthed={this.isAuthed} />
) : (
<Fragment>
<Header unAuth={this.unAuth} />
{this.props.children}
</Fragment>
);
}
export default withRouter(RequireAuth);
Or, instead of wrapping routes, you can create a protected component that houses protected routes.
Working example: https://codesandbox.io/s/yqo75n896x (uses redux instead of local state).
routes/index.js
import React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import { createStore } from "redux";
import { Provider } from "react-redux";
import Home from "../components/Home";
import Header from "../containers/Header";
import Info from "../components/Info";
import Sponsors from "../components/Sponsors";
import Signin from "../containers/Signin";
import RequireAuth from "../containers/RequireAuth";
import rootReducer from "../reducers";
const store = createStore(rootReducer);
export default () => (
<Provider store={store}>
<BrowserRouter>
<div>
<Header />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/info" component={Info} />
<Route path="/sponsors" component={Sponsors} />
<Route path="/protected" component={RequireAuth} />
<Route path="/signin" component={Signin} />
</Switch>
</div>
</BrowserRouter>
</Provider>
);
containers/RequireAuth.js
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import ShowPlayerRoster from "../components/ShowPlayerRoster";
import ShowPlayerStats from "../components/ShowPlayerStats";
import Schedule from "../components/Schedule";
const RequireAuth = ({ match: { path }, isAuthenticated }) =>
!isAuthenticated ? (
<Redirect to="/signin" />
) : (
<div>
<Route exact path={`${path}/roster`} component={ShowPlayerRoster} />
<Route path={`${path}/roster/:id`} component={ShowPlayerStats} />
<Route path={`${path}/schedule`} component={Schedule} />
</div>
);
export default connect(state => ({
isAuthenticated: state.auth.isAuthenticated
}))(RequireAuth);
You can even get more modular by creating a wrapper function. You would be able to pick and choose any route by simply wrapping over the component. I don't have a codebox example, but it would be similar to this setup.
For example: <Route path="/blog" component={RequireAuth(Blog)} />
You spelled Autheenticated wrong.
Also, this is an assumption because you only provided the stack trace and not the above error, which probably says AuthenticationService.IsAutheenticated is not a function.
I want to be able to have a Portfolio page (example.com/portfolio), and a dynamic route for individual case studies (example.com/portfolio/case-study/dynamic-url-slug). Currently, the new component that should render in its own page is still rendering within the page (understandable, as the markup declares the route within the containing div). But how do I get it to render on its own page?
App.js (where all routes are declared)
import React, { Component } from 'react';
import { Switch, Route } from 'react-router-dom';
import Home from './components/Pages/Home/Home';
import About from './components/Pages/About/About';
import Portfolio from './components/Pages/Portfolio/Potfolio';
import CaseStudy from './components/Pages/Portfolio/CaseStudyPage';
export default class App extends Component {
render() {
return (
<div className="icon-container" id="outer-container">
<div className="pages">
<Switch>
<Route exact path='/' component={ Home } />
<Route path='/about' component={ About } />
<Route path='/portfolio' component={ Portfolio } />
<Route exact path={`/portfolio/case-study/:caseSlug`}
render={(props) => <CaseStudy />} />
</Switch>
</div>
</div>
)
}
}
Portfolio.js
import React, { Component } from 'react';
import '../styles/vendor/swiper/swiper.min.css';
import Swiper from 'react-id-swiper';
import { Link, Route } from 'react-router-dom';
import CaseStudyPage from './Pages/Work/CaseStudyPage';
const case_studiesURL = "http://myprivateblogapi.com/wp-json/wp/v2/case_studies?_embed";
const case_URL = '/portfolio/case-study/';
export default class Portfolio extends Component {
constructor(props) {
super(props);
this.state = {
case_studies: [],
isLoading: true,
requestFailed: false
}
}
componentDidMount() {
fetch(case_studiesURL)
{/* fetching all the appropriate data */}
}
renderPortfolioItem(data) {
return props => <CaseStudyPage data={data} {...props} />
}
render() {
if(this.state.isLoading) return <span>Loading...</span>
const params = {
{/* swiper parameters */}
}
let case_studies_items = this.state.case_studies.map((case_studies_item, index) => {
return (
<div className="portfolio-slide" id={`swiper-slide-${index}`}
key={index}
>
<Link className="portfolio-link"
to={`${case_URL}${case_studies_item.slug}`}>
<h3 className="portfolio-swiper--slide-title"> {case_studies_item.title.rendered}</h3>
</Link>
<Route exact path={`${case_URL}:caseSlug`}
render={this.renderPortfolioItem(case_studies_item)} />
</div>
)
});
return(
<div className="portfolio-swiper--container">
<Swiper {...params}>
{case_studies_items}
</Swiper>
</div>
)
}
}
You should define a route for each different views in react router,
<Switch>
<Route exact path='/' component={ Home } />
<Route exact path='/about' component={ About } />
<Route exact path='/portfolio' component={ Portfolio } />
<Route exact path='/portfolio/case-study' component={ CaseStudy } />
<Route exact path='/portfolio/case-study/:caseSlug' component {CaseStudyDetails} />
</Switch>
and you don't need to create a render method to pass props to your view components. You can easily reach router props inside of a react component if it is already rendered into Router,
this.props.match
this.props.location
this.props.history
as an example you can get your dynamic parameter inside of CaseStudy component like,
this.props.match.caseSlug