React JS component with nested navigation - react-router

I've been having some trouble with the following. I've tried for a few hours to find some help through searching, but have come up empty handed. It could be that I don't know the name of what I'm trying to do, but I figured I'd ask here.
I've been learning React for about a month. Currently I'm trying to create a component which has it's own navigation bar and displays it's content based on which link in the component's navigation bar is clicked.
I have a Navigation bar for the entire website, using React Router, and I've tried nesting the component's route in the Route for the page I want it displayed, but when I click on a link within said component, instead of simply having the content displayed within that component, I'm navigated to a new page (in this case: localhost3000/#/project1). That new page displays the entire component, with the correct content. However, I want to avoid navigating to a new page.
Here's a pic of what I want to do.
Here's some of the code I've got so far. (I've omitted the imports and anything else unnecessary.
My index.js
ReactDOM.render(
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRoute component={OtherPage}></IndexRoute>
<Route path="Project_Page" component={ProjectPage} />
<Route component={ProjectsComponent}>
<Route path="project1" component={Project1} />
<Route path="project2" coponent={Project2} />
<Route path="project3" coponent={Project3} />
</Route>
<Route path="Another_Page" component={AnotherPage}></Route>
</Route>
</Router>
,
document.getElementById('root')
);
My ProjectPage.js
export default class ProjectPage extends Component{
render(){
return(
<div>
<ProjectsComponent />
</div>
);
}
}
My ProjectsComponent.js
export default class ProjectsComponent extends Component{
render(){
return(
<div>
<ProjectsNav /> // this is the navbar for my projects component
<div>
{this.props.children}
</div>
</div>
);
}
}
My ProjectsNav.js
export default class ProjectsNav extends Component{
render(){
return(
<div>
<Link to="Project1" className="btn btn-primary">Project 1</Link>
<Link to="Project2" className="btn btn-primary">Project 2</Link>
<Link to="Project3" className="btn btn-primary">Project 3</Link>
</div>
);
}
}
Finally
My Project1.js project2 && project3 are pretty much the same thing.
export default class Project1 extends Component{
render(){
return(
<div className="project">
Hello from Project 1
</div>
);
}
}
I'm sorry if this is something that's already been covered. If it has, please feel free to point me in the right direction. That's really all I need.
Thank you so much for your help.

ProjectsComponent component is not required, Try these components:
ReactDOM.render(
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRoute component={OtherPage}></IndexRoute>
<Route path="Project_Page" component={ProjectPage}>
//<Route component={ProjectsComponent}>
<IndexRoute path="Project_Page/Project1" component={Project1} />
<Route path="Project_Page/Project2" coponent={Project2} />
<Route path="Project_Page/Project3" coponent={Project3} />
</Route>
<Route path="Another_Page" component={AnotherPage}></Route>
</Route>
</Router>
,
document.getElementById('root')
);
export default class ProjectPage extends Component{
render(){
return(
<div>
<ProjectsNav />
<div>
{this.props.children}
</div>
</div>
);
}
}
export default class ProjectsNav extends Component{
render(){
return(
<div>
<Link to="Project_Page/Project1" className="btn btn-primary">Project 1</Link>
<Link to="Project_Page/Project2" className="btn btn-primary">Project 2</Link>
<Link to="Project_Page/Project3" className="btn btn-primary">Project 3</Link>
</div>
);
}
}
Let me know if u face any issue or want any help.

Related

Main content wrapper best practices

I want to create a main content wrapper so I don't have to add classes to each component separately, I use tailwindCSS. It is just a couple of classes, mainly to give the content a margin, a max width and keep it centered.
I put a couple of divs encompasing the routes, but I don't know if this is considered a good practise.
Here is my App.jsx
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
// pages
import Navbar from './Navbar';
import Home from './Home';
import About from './About';
import Contact from './Contact';
function App() {
return (
<Router>
<div className='font-mono'>
<Navbar />
<div className='flex justify-center'>
<div className='m-10 lg:max-w-4xl'>
<Routes>
<Route path='/' element={<Home />}/>
<Route path='/about' element={<About />} />
<Route path='/contact' element={<Contact />} />
</Routes>
</div>
</div>
</div>
</Router>
);
}
export default App;
Yeah I imagine this is pretty common. I typically abstract it in a separate component.
/components/container/Container.js
export default function Container({ children }) {
return (
<div className='flex justify-center'>
<div className='m-10 lg:max-w-4xl'>{children}</div>
</div>
);
}
You can then import it and use it wherever you'd like. In the case of wrapping your routes I suppose you won't be reusing it elsewhere, but it does still remove some visual noise and keep things a little organized.
/App.js
import Container from './components/container/Container';
function App() {
return (
<Container>
<Router>
<Routes>
<Route path='/' element={<Home />}/>
<Route path='/about' element={<About />} />
<Route path='/contact' element={<Contact />} />
</Routes>
</Router>
</Container>
);
}

Can we put html inside Route element?

<Router>
<Routes>
<Route path='/' element={<Navbar />} />
<Route path='/' element={<div className='recipes'>
{query ? query.map((object, i) => (
<RecipeCard
src={object.src}
ingredients={object.ingredients}
steps={object.steps}
key={i}
/>
)) : "Loading"}
</div>}/>
<Route path='/' element={<Details />} />
</Routes>
</Router>
For example in the above code, I want to render the HTML along with the Route element.
I am not getting the 2nd and 3rd Route tags displayed on my localhost. Where is my mistake?
What is the correct way to do this?
The only requirement is that the element prop takes a React.ReactNode. In other words, it takes any valid JSX.
The issue though is that there can only be one route per path. Your code is trying to render 3 routes on the same "/" path. Just unconditionally render the Navbar and Details components not on a route.
Example:
<Router>
<Navbar />
<Routes>
<Route
path='/'
element={(
<div className='recipes'>
{query
? query.map((object, i) => (
<RecipeCard
src={object.src}
ingredients={object.ingredients}
steps={object.steps}
key={i}
/>))
: "Loading"
}
</div>
)}
/>
</Routes>
<Details />
</Router>
If you are wanting to conditionally render Navbar and Details on only certain routes then create a layout route component.
Layout Route
Outlet
Example:
import { Outlet } from 'react-router-dom';
const Layout = () => (
<>
<Navbar />
<Outlet /> // <-- nested routes render element content here
<Details />
</>
);
Render the routes you want to have the navbar and details as nested routes, and for the routes you don't want them render these as sibling routes.
<Router>
<Routes>
<Route element={<Layout />}>
<Route
path='/'
element={(
<div className='recipes'>
{query
? query.map((object, i) => (
<RecipeCard
src={object.src}
ingredients={object.ingredients}
steps={object.steps}
key={i}
/>))
: "Loading"
}
</div>
}
/>
...other routes with Navbar and Details...
</Route>
...other routes w/o Navbar and Details...
</Routes>
</Router>

What's the best way to change a global component based on the current route?

I'm building a website with React and I would like my navbar to have a different background color depending on the current route. My App.js looks like this:
class App extends React.Component {
render() {
return (
<Router>
<div className="App">
<div className="Navbar" >
<Navbar />
</div>
<div className="Content">
<Route path="/" exact render={() => <LandingPage />} />
<Route path="/about" exact render={() => <AboutPage />} />
<Route path="/contact" exact render={() => <ContactPage />} />
</div>
</div>
</Router>
);
}
}
Suppose I want a transparent background on the Home ("/") page, but a solid color everywhere else, what is the best way to go about changing the background property in <Navbar />'s CSS to achieve what I want?
Suppose I stored the CSS properties I'd like to change in this.state, and then call a function to change these whenever there is a route change?
You can use context api for changing the background color of any of your components. So, you just need to define a context value based on the different urls.

Make Switch component respect URL fragments with React Router?

How can I do hash routing with React Router?
This works fine:
return (
<div>
<div>
<Link to="/one">One</Link>
<Link to="/two">Two</Link>
</div>
<Switch>
<Route exact path="/one" component={One} />
<Route exact path="/two" component={Two} />
<Route component={Three} />
</Switch>
</div>
);
However this always renders component Three:
return (
<div>
<div>
<Link to="/#one">One</Link>
<Link to="/#two">Two</Link>
</div>
<Switch>
<Route exact path="/#one" component={One} />
<Route exact path="/#two" component={Two} />
<Route component={Three} />
</Switch>
</div>
);
Do I need the hash router for this? I don't find the docs very clear:
https://reacttraining.com/react-router/web/api/HashRouter
Component Three is rendered because that's the default one whenever there is no routing point that matches a given URL.
You're right, you should use HashRouter provided by the react-router-dom package. Based on the documentation, the appropriate configuration should be something similar to this in your case:
import { HashRouter } from 'react-router-dom';
<HashRouter basename="/" hashType="noslash">
<App />
</HashRouter>
Thereby the root path is changed to /#.
To stick to your example:
<div>
<div>
// http://localhost:3000/#one
<Link to="/#one">One</Link> // This can be either /one or /#one
<Link to="/#two">Two</Link>
</div>
<Switch>
<Route exact path="/one" component={One} />
<Route exact path="/two" component={Two} />
<Route component={Three} />
</Switch>
</div>
Unfortunately, the path prop should be the same as before.
The documentation also contains an example of how the router creates the href links in this situation.

Trouble migrating to react-router v4

I am having trouble migrating to react router 4 with nested routes. Here was some snippets from my previous code. was my layout container and everything was rendered within that if logged in (otherwise redirect to login)
ReactDOM.render(
<Provider store={ store }>
<div>
<Router history={ history }>
<Route path='/login' component={ Login } />
<Route path="/password/reset" component={PasswordReset} />
<Route path='/register' component={ Register } title={ 'Register' } />
<Route path='/password/change/:token' component={ ChangePassword } title={ 'Register' } />
<Route component={ EnsureLoggedInContainer }>
<Redirect from='/' to='/dashboard' />
<Route path='/' component={ App }>
<Route path='/logout' component={ Logout } />
....
</Router>
</div>
within to render the children components:
class ContentLayout extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
let children = null;
if (this.props.children) {
children = React.cloneElement(this.props.children, {
updateHeader: this.props.updateHeader,
});
}
return (
<div className={ this.props.cls }>
<MainHeader
updateHeader={ this.props.updateHeader }
header={ this.props.header }
/>
{children}
</div>
);
}
}
With v4 I've been trying to figure out the proper way to render as the layout and any child components within. So far I've been trying to get it working but feel I am on wrong path. (Currently props.match always points to '/')
ReactDOM.render(
<Provider store={ store }>
<ConnectedRouter history={history}>
<div>
<Switch>
<Route exact path='/login' component={ Login } />
<Route exact path="/password/reset" component={PasswordReset} />
<Route exact path='/register' component={ Register } />
<Route exact path='/password/change/:token' component={ ChangePassword } />
<Route path='/' component={ App } />
</Switch>
</div>
</ConnectedRouter>
</Provider>,
document.getElementById('app')
);
Within App
const RouteWithProps = ({ component: Component, props, ...extraProps})=> {
return (<Route
{...extraProps}
render={() => <Component {...props} {...extraProps} />}
/>
);
}
and with the component rendering
{securedRoutes.map((route, i) => (
<RouteWithProps key={i} {...route} updateHeader={this.props.updateHeader} location={this.props.location} match={this.props.match} />
))}
What is the proper way or a good example of how to structure the app so for all logged in routes the layout is
<App>
<ContentLayout>
<Child>
with App passing props like updateHeader and anything else to all children.
I got it working by removing passing location and match to RouteWithProps.
I had an issue with the RouteWithSubRoutes example in dealing with nested routes for things like /.../:id and /.../:submit ended up doing this to make work so I can continue working. Do not think this is ideal but will work till another answer on best practices.
const RouteWithProps = ({ component: Component, ...extraProps }) => {
return (<Route exact path={extraProps.path}
{...extraProps}
render={matchProps => <Component {...matchProps} {...extraProps}
/>}
/>
);
}
Also removed passing this.props.location and match to this component.