Right now I have a Footer component with three different buttons. The onClick of two of those buttons depends on the page, and are defined in those pages. Currently I render the Footer component in the Page components separately, where the onClick functions are defined and handed as a prop, and is working great.
However I added the CSS element max-width to the pages, and is affecting the Footer too, which I don't want.
To solve this I need to render the Footer outside of the Page components, and in the Router component, just like the Header. But the issue is that I don't know how to link the click handlers of those two Footer buttons, which are defined in the Page components.
What would be the best way to solve this? Push the onClick functions in my Redux store?
Currently it's working like this:
Router.js
<div id="router-container">
<Header />
<Switch>
<Route path="/dashboard" component={DashboardPage} />
<Route path="/settings" component={SettingsPage} />
</Switch>
</div>
DashboardPage.js
const DashboardPage = () => (
<div id="dashboard-page">
...content
<Footer onRightButtonClick={myDashboardPageClickHandler} />
</div>
);
SettingsPage.js
const SettingsPage = () => (
<div id="settings-page">
...content
<Footer onRightButtonClick={mySettingsPageClickHandler} />
</div>
);
But how can I get it to work like this:
Router.js (new)
<div id="router-container">
<Header />
<Switch>
<Route path="/dashboard" component={DashboardPage} />
<Route path="/settings" component={SettingsPage} />
</Switch>
<Footer />
</div>
DashboardPage.js / SettingsPage.js
const DashboardPage = () => {
// set the onClick of the footer somehow to myDashboardPageClickHandler()
return (
<div id="dashboard-page">
...content
</div>
);
};
I would recommend using the withRouter function of React Router to transform the Footer, like so:
const FooterWithRouter = withRouter(Footer);
then, inside the Footer component, define functions like so:
handleRightButtonClick() {
const pathname = { this.props.location };
if ( pathname === '/dashboard' ) {
return myDashboardPageClickHandler;
} else if ( pathname === '/settings' ) {
return mySettingsPageClickHandler;
}
}
and assign the onClick of the button to the return value of this function, like so:
<button class="right-arrow" onClick={handleRightButtonClick()}></button>
See if this works.
Related
This question already has answers here:
How do I render components with different layouts/elements using react-router-dom v6
(2 answers)
Closed 10 months ago.
I want to hide Header and Footer Component in some routes, e;g for path="/invoice/:id/print"
I have an app with this type of layout using react-router-dom v5
<Router>
<Header />
<main className="py-0 px-0">
<div>
<Container fluid>
<Switch>
<Route path="/" component={HomeScreen} exact />
<Route path="/invoices" component={Invoices} exact />
<Route path="/invoice/:id/print" component={InvoicePrint} exact />
<Route path="/billing/invoice/create" component={InvoiceCreate} exact />
<Route path="*" exact>
<Error404 />
</Route>
</Switch>
</Container>
</div>
</main>
<Footer />
</Router>
The problem is if I go to
<Route path="/invoice/:id/print" component={InvoicePrint} exact />
Then Header and Footer also get rendered. But I want to hide them for this specific route. So how can I do that?
I'm using react-router version 5
That depends on how many pages should render the Header and Footer components.
If you want to render them on just a few pages, the simplest solution will be to move them to the components/pages where you'd like to render them.
If you'd like to hide them in just one or two places you can use the useRouteMatch hook.
You can also build some abstractions to simplify the readability a bit: let's make the Layout component, which will accept a prop (like renderHeaderAndFooter (you can change the name of course 😄)).
Layout component:
const Layout = ({ renderHeaderAndFooter, children }) => (
<div>
{renderHeaderAndFooter && (
<Header />
)}
{children}
{renderHeaderAndFooter && (
<Footer />
)}
</div>
)
and the place of usage:
const HomeScreen = () => (
<Layout renderHeaderAndFooter={true}>
// make stuff
</Layout>
)
const Invoices = () => (
<Layout renderHeaderAndFooter={false}>
// make stuff
</Layout>
)
simple way is to trigger your current route by using useRouteMatch in every component you want like:
const match = useRouteMatch("/invoice/:id/print"); // import { useRouteMatch } from "react-router-dom";
return !!match ? <></> : <Footer/> //or Header
check this
You have to update the Header and Footer components by adding a listener during the rendering phase of the component : componentDidMount()
Fetch the called component :
const shouldHide = useRouteMatch("/invoice/:id/print") || useRouteMatch("/other/route");
return (
!shoudlHide && <Header />
)
Any help would be appreciated, So i have a page with Header, Sidebar, Footer and Main, where Sidebar has static links, which when clicked display the components. The issue here is on clicking the links, sidebar,header and footer are re-rendering which is not required. I have tried shouldComponentUpdate in sidebar but it won't work.
Versions used by the project:
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-router-dom": "^5.1.2",
I'll be here till this issue is resolved so feel free to ask any question
here is myApp.js (the root file)
function App() {
return (
<Provider store={Store}>
<Router history={history}>
<AppRoutes />
</Router>
</Provider>
);
}
now the AppRoutes component has following method
const RouteList = [
{
path: "/",
component: Dashboard,
guest: false,
exact: true
},
{
path: "/security_info",
component: SecurityInfoPage,
guest: false,
exact: true
},
]
class AppRoutes extends Component {
componentDidMount() {
...here we fetch the login info from store
isAuthenticated = true
}
render() {
...if it has access token, it
return (
<Switch>
{RouteList.map((route, i) => (
route.guest === false
? <PrivateRoute isAuthenticated={isAuthenticated} key={i} {...route} />
: <AppRoute key={i} {...route} />
)
)}
</Switch>
);
}
}
as is_authenticated is true, it goes to private route inside AppRoute.js file
const PrivateRoute = ({isAuthenticated, component: Component, ...rest }) => (
<Route
{...rest}
render={(props) => (
isAuthenticated === true
? <DashboardLayout>
<Component {...props}/>
</DashboardLayout>
: <Redirect to='/login' />
)}
/>
)
it goes to dashboardlayout where it has multiple components
<div className={'wrapper'}>
<Navigation />
<div className="page-content">
<Sidebar />
<div className="content-wrapper">
{children}
<MessageSideBar />
<Footer />
</div>
</div>
</div>
Now as i click on a different link, it goes to dashboard layout where its prop children gets changed rendering the entire dashboard including header, footer, sidebar.
Edit 1:
Here is the Sidebar file
class Sidebar extends Component {
componentDidMount = () => {
it is requesting data from 3 api's
this.props.dispatch(sidebarAction.sidebarDetail())
this.props.dispatch(settingAction.getCreditAmount())
this.props.dispatch(messageAction.getUnReadMessageCount())
}
render(){
return(
<ul>
<li>
<NavLink
exact={true}
to="/" >
<span>Dashboard</span>
</NavLink>
</li>
<li>
<NavLink to="/security_info">
<span>Security Information</span>
</NavLink>
</li>
</ul>
)}
Though there are like 10+ NavLinks but i have included only 2 and also removed irrelevant classnames
Incorrect way
Your routes are structured like the following snippet, which will cause rerendering the Dashboard component every time you switch the route.
<Route path="/subComponent" component={SubComponent}/>
const SubComponent = () => {
return (
<Dashboard>
// Here is your content place
</Dashboard>
)
}
Correct way
However, the correct solution would be to put the routes directly inside the Dashboard component where you would like to render your components wrapped by the layout (Dashboard) like this:
<Dashboard>
<DashboardMenu>
<SideNav /> // e.g. containing links with history.push()
</DashboardMenu>
// here is the place where the components wrapped by the layout should be rendered, so place the routes here
<Route path={...} component={() => <ComponentInsideDashboard />}
</Dashboard>
You should render the actual content (the dynamic one, not the static Dashboard) already inside the Dashboard. Since every route returns the dynamic components wrapped inside Dashboard, the Dashboard will get rendered multiple times.
To put it even more simply: Since you want the Dashboard to get rendered only once, there should just be only one place where you are using it.
Correct way with different layouts
If you also want to render content without a layout (Dashboard) or multiple different ones, you can simply make use of nesting routes.
export const BaseApplicationRoutes = () => {
return (
<BrowserRouter>
<Switch>
<Route path="/dashboard1" component={<Dashboard1 />}/>
<Route path="/dashboard2" component={<Dashboard2 />}/>
<Route path="/noDashboard" component={<NoDashboard />}/>
</Switch>
</BrowserRouter>
)
}
<Dashboard1> // Dashboard2 could also look like this
<Dashboard1Menu>
<SideNav /> // e.g. containing links with history.push()
</Dashboard1Menu>
// Routes containing dynamic content
<Route path={...} component={() => <ComponentWithDashboard1 />}
</Dashboard1>
<NoDashboard>
// any content or even more routes rendering content without a layout
</NoDashboard>
I have two different components in React "Header" and "Main".
Header:
import React, { Component } from 'react'
import Logo from './HeaderComps/Logo'
import UserHeader from './HeaderComps/UserHeader'
export default class Header extends Component {
render() {
return (
<header>
<Logo />
<UserHeader name ="Boris"/>
</header>
)
}
}
Main:
export default class Main extends Component {
state ={isLogged : false}
handleClientFirstImpact = () =>{
if(this.state.isLogged === false){
return <Switch>
<Route exact path ='/registration' component={Register} />
<Route exact path ='/login' component={Login} />
</Switch>
}
}
render() {
return (
<Router>
<div className="Main">
{this.handleClientFirstImpact()}
</div>
</Router>
)
}
}
In "Main" I have two components "Register" and "Login".
How do I make Login page effect the Header's state? is there a React technology or a way to do that?
I want the "UserHeader" to show the User name but I don't know how to effect it's parent's state.
There might be some common component where you will be calling the Main as well as the Header Component. Suppose that component is App Component.
So inside App you have
render() {
return
<div>
<Header />
<Main />
</div>
}
Now what you can do is keep the userName in this App Component's state and this App Component will pass userName to the Component as :-
<Header userName={userName} />
Also pass a function as a prop to the Main Component which will enable the component to set the State of the Parent Component.
<Main setUserName={(newUserName) => this.setState(newUserName)} />
now this setUserName prop should be passed on to the components which are called via Route inside the Main Component. Keeping your example in mind (use render prop instead of component for Route):
export default class Main extends Component {
state ={isLogged : false}
handleClientFirstImpact = () =>{
const { setUserName } =this.props;
if(this.state.isLogged === false){
return
<Switch>
<Route exact path ='/registration'
render={(props) => <Register {...props} setUserName={setUserName} />}
/>
<Route exact path ='/login'
render={(props) => <Login {...props} setUserName={setUserName} />}
/>
</Switch>
}
}
render() {
return (
<Router>
<div className="Main">
{this.handleClientFirstImpact()}
</div>
</Router>
)
}
}
Now you have passed setUserName as a prop to both login and register and you can use this method to set App component's state which will in turn reflect the changes on the Header component.
Although the solution might work for you. I would advise you to simplify the Application layout. Keep the routing functionality in the main app Component. Use a separate layout component to render similar page layouts. It would avoid confusion in the long run.
I have 2 page components with their own routes. There is one header component that is to be used in both pages, and it is supposed to change its content depending on what page you're on.
How can I change the content in the header per page?
return (
<main className={theme.main}>
<Header
title={this.state.headerTitle}
boldTitle={this.state.boldTitle}
/>
<div className={theme.content}>
<Switch>
<Route path={'/page1'} component={Page1} />
<Route path={'/page2'} component={Page2} />
</Switch>
</div>
<Footer/>
</main>
);
I am trying to pass props to the header in my Routes page as in the code below.
<Route path={'/page2'} component={Page2} currentTitle={this.state.headerTitle} />
UPDATE
I got it fixed this using the
import { withRouter } from "react-router"; and
export default withRouter(Header);
in Header Component then he listen the routes and I can put the pathname for to do what I need.
Then, I don't need set up state, just listen the routes inside that MyComponent.
Usign: this.props.location
MOVE YOUR HEADER INSIDE Page1 AND Page2.
<Route path={'/page2'} component={() => <Page2 currentTitle={this.state.headerTitle}
/>} />
Then in header use currentTitle to change title.
Put Header into its own component and import it separately into each page component.
Then you can pass props into the header like this, when you call the Header in each page...
import Header from (wherever that component is)
class Page# extends React.component {
other code
render() {
return (
<div>
<Header currentTitle="this.state.headerTitle" />
<rest of code for this page />
</div>
}
or whatever props you need.
I assume that the code you show lies inside the render() method of some React component, so that you have actually a state.
Note that the component attribute in the Route tag expects a value that is a function without arguments. The component to be rendered is the result of the function. But nothing forbids you to make some work before returning a value. I would try something like this
<Route path={'/page1'} component={() => {
this.setState({headerTitle: ...what you want..., boldTitle: ...what you want...})
return <Page1 />
}}/>
If Page1 is just a function, change the "return" line with return Page1()
Hope it helps - Carlos
you can use the redux library as global state
something like this
class Main extends Component{
render(){
return (
<main className={theme.main}>
<Header
title={this.props.headerTitle}
boldTitle={this.state.boldTitle}
/>
<div className={theme.content}>
<Switch>
<Route path={'/page1'} component={Page1}/>
<Route path={'/page2'}component={Page2}
/>
</Switch>
</div>
<Footer/>
</main>
)
}
const mapStateToProps = state =>{
return { headerTitle: state.headerTitle }
}
export default connect(mapStateToProps)(Main)
Page1
class Page1 extends Component{
changeTitle =()=>{
this.props.actions.changeTitle("title from Page1")
}
render(){
return (
<div>
<button onClick={this.changeTitle}>changeTitle</button>
</div>
)
}
const mapDispatchToProps = (dispatch)=> {
return {
actions: bindActionCreators({ changeTitle }, dispatch)
}
}
export default connect(null, mapDispatchToProps)(Page1)
the code it's not functional but this is an Idea how you can use redux for this purpose
I am somewhat new to React. so please bear with me. I have the following base structure:
<App>
this.props.children
</App>
...and in children, one component is a header that has what I want to be an optional search component:
<Header>
...some other children...
<Search /> <- STUCK HERE SHOULD BE OPTIONAL!!!
</Header>
...children from other components...
What I am trying to do is say when I go to route A, the search component should not be included (or at least not shown), but when I go to route B, it should. I have scoured for days and so far been unable to find a solution that meets this need. If it matter, I am using ES6/7 (babel via webpack).
I can set state in the APP and toggle it literally to adjust the passed down props on the Search and show or not show it, but cannot figure out how to do that dynamically based on the route.
The core issue is how to tell App (and indirectly Header) to show the search component on inside the Header on some routes, but not on others. I 'think' maybe I need some sort of abstraction/wrapper component in the middle, but am not really sure. Any though or ideas are welcome.
TIA!
First setup your routes.
<Router path="/" component={App}>
<Route path="foo" component={Header} showSearch={true} />
<Route path="bar" component={Header} showSearch={false} />
</Router>
The route will be passed down as a property, then you can access the showSearch property, which determines whether the search component is rendered.
// Header
render() {
const { showSearch } = this.props.route;
return (
<div className='header'>
// ... other components
{ showSearch ? <Search /> : null }
</div>
);
}
Maybe you don't want your header to be the top level component though. In that case define an intermediary wrapper component that forwards the route props down to the header.
<Router path="/" component={App}>
<Route path="foo" component={Shell} showSearch={true} />
<Route path="bar" component={Shell} showSearch={false} />
</Router>
// Shell
render() {
const { route } = this.props;
return (
<div className='shell'>
<Header {...route} />
</div>
);
}
Alternatively, you could do a quick and dirty check from inside your Header component.
// Header
render() {
const { hash } = window.location,
showSearch = /\/foo/.test(hash);
return (
<div className='header'>
// ... other components
{ showSearch ? <Search /> : null }
</div>
);
}
If you want to use functional components, React Router has specifically created an API for this called render for this purpose.
Example:
<Route
path='/search'
render={(props) => (
<Header {...props} showSearch={true} />
)}
/>
Then just simply use the props as normal in your component:
interface HeaderProps {
showSearch: boolean
}
export const Header: React.FC<HeaderProps> = ({ showSearch }) => {
return (
<React.Fragment>
{ showSearch ? <Search /> : null }
</React.Fragment>
)
}
See the excellent article written by Tyler McGinnis regarding this implementation:
https://ui.dev/react-router-v4-pass-props-to-components/