React: Hide a Component on a specific Route
Asked Answered
R

6

28

New to React:

I have a <Header /> Component that I want to hide only when the user visit a specific page.

The way I designed my app so far is that the <Header /> Component is not re-rendered when navigating, only the page content is, so it gives a really smooth experience.

I tried to re-render the header for every route, that would make it easy to hide, but I get that ugly re-rendering glitch each time I navigate.

So basically, is there a way to re-render a component only when going in and out of a specific route ?

If not, what would be the best practice to achieve this goal ?

App.js:

class App extends Component {

  render() {
    return (
      <BrowserRouter>
        <div className="App">
          <Frame>
            <Canvas />
            <Header />
            <Main />
            <NavBar />
          </Frame>
        </div>
      </BrowserRouter>
    );
  }
}

Main.js:

const Main = () => (
  <Switch>
    <Route exact activeClassName="active" path="/" component={Home} />
    <Route exact activeClassName="active" path="/art" component={Art} />
    <Route exact activeClassName="active" path="/about" component={About} />
    <Route exact activeClassName="active" path="/contact" component={Contact} />
  </Switch>
);
Runic answered 9/6, 2018 at 18:27 Comment(2)
What re-render glitch?Understructure
It was like the whole app re-loading. A 10ms flash when navigatingRunic
I
29

I'm new to React too, but came across this problem. A react-router based alternative to the accepted answer would be to use withRouter, which wraps the component you want to hide and provides it with location prop (amongst others).

import { withRouter } from 'react-router-dom';    
const ComponentToHide = (props) => {
  const { location } = props;
  if (location.pathname.match(/routeOnWhichToHideIt/)){
    return null;
  }

  return (
    <ComponentToHideContent/>
  )
}

const ComponentThatHides = withRouter(ComponentToHide);

Note though this caveat from the docs:

withRouter does not subscribe to location changes like React Redux’s connect does for state changes. Instead, re-renders after location changes propagate out from the component. This means that withRouter does not re-render on route transitions unless its parent component re-renders.

This caveat not withstanding, this approach seems to work for me for a very similar use case to the OP's.

Inextricable answered 31/3, 2019 at 22:39 Comment(1)
Since React Router 5.1 the hook useLocation() can be usedProthonotary
P
13

Since React Router 5.1 there is the hook useLocation, which lets you easily access the current location.

import { useLocation } from 'react-router-dom'

function HeaderView() {
  let location = useLocation();
  console.log(location.pathname);
  return <span>Path : {location.pathname}</span>
}
Prothonotary answered 30/4, 2020 at 18:42 Comment(2)
This answer saved scratching my head over this issue. Thanks!Ole
Works like charm.Monique
C
10

You could add it to all routes (by declaring a non exact path) and hide it in your specific path:

<Route path='/' component={Header} /> // note, no exact={true}

then in Header render method:

render() {
  const {match: {url}} = this.props;

  if(url.startWith('/your-no-header-path') {
    return null;
  } else {
    // your existing render login
  }
}
Coadunate answered 9/6, 2018 at 18:35 Comment(2)
Thanks. I did edit my questions with more details... would that solution still apply ?Runic
Usually path='/' is a webapp's home page. I think it makes more sense for the component to be a Layout component and inside of that component should be the logic to display the header or not.Ruelle
C
4

You can rely on state to do the re-rendering.

If you navigate from route shouldHide then this.setState({ hide: true })

You can wrap your <Header> in the render with a conditional:

{
  !this.state.hide &&
  <Header>
}

Or you can use a function:

_header = () => {
  const { hide } = this.state
  if (hide) return null
  return (
    <Header />
  )
}

And in the render method:

{this._header()}

I haven't tried react-router, but something like this might work:

class App extends Component {

  constructor(props) {
    super(props)
    this.state = {
      hide: false
    }
  }

  toggleHeader = () => {
    const { hide } = this.state
    this.setState({ hide: !hide  })
  }

  render() {

    const Main = () => (
      <Switch>
        <Route exact activeClassName="active" path="/" component={Home} />
        <Route
          exact
          activeClassName="active"
          path="/art"
          render={(props) => <Art toggleHeader={this.toggleHeader} />}
        />
        <Route exact activeClassName="active" path="/about" component={About} />
        <Route exact activeClassName="active" path="/contact" component={Contact} />
      </Switch>
    );

    return (
      <BrowserRouter>
        <div className="App">
          <Frame>
            <Canvas />
            <Header />
            <Main />
            <NavBar />
          </Frame>
        </div>
      </BrowserRouter>
    );
  }
}

And you need to manually call the function inside Art:

this.props.hideHeader()

Chinchin answered 9/6, 2018 at 18:36 Comment(11)
I did edit my question with some code so the way I designed the layout and routing is more clear. Maybe the problem comes from that...So, I will have to update the app state from the <Art /> component.. that is the one I want to hide the header... and then set the state to hide:false in every other route ?Runic
I am a bit unclear how you route here. Do you change the content somewhere? What happens when you click on the route?Chinchin
First time I use Router... I decided to have all the page content in <main> and switch the content with Router with path="/" component={Home} ... path="/about" component={About} etc etc ... that would work great if I didnt want to get rid of <header> on just one specific route... so it's kind of a design problem as well I guessRunic
Can you link the Router package? I haven't used router before so this might not apply. But what you can do is to pass a function that modifies App's Header state to the route. And have the child call that function and directly modify the parent's header state.Chinchin
I guess I'll have to look into the Context API or Redux to do this right? Well, maybe not Redux just for that...Runic
I looked at react-router. You can pass a function to it, so hopefully you can affect the parent this way. I updated my answer with how this might work, hopefully that'll help.Chinchin
Almost there Dan... I have an infinite loop problem with this solution. When clicking on the Art link, the state update to 'hide', the component <Art/> re-render and run the toggle function again, the state updates to '!hide' ... re-render ... etc etc.... how can I avoid this ?Runic
If possible, can you post your code? I haven't used router, so can you confirm that the constructor for Art is only triggered once on render? It shouldn't trigger multiple times when you click on Art. Another thing to try is to split the toggleHeader function into hideHeader and showHeader for specific routes. This should at least resolve the issue of it alternating between show and hide. But the main thing here might be to resolve and confirm that Art's constructor and componentDidMount runs only once on click.Chinchin
constructor runs in infinite loop yesRunic
is there a way I can pass a parameter to this props ( false or true) and then update it on ComponentWillMount and ComponentWillUnmount lifecycle methods ?Runic
I think I am going to look into Redux or the context API to keep track of the state easier. I discovered the withRouter method that allow to keep track of the page visited... if I wrap the App with it it should be easyRunic
E
0
{location.pathname !== '/page-you-dont-want' && <YourComponent />}

This will check the path name if it is NOT page that you DO NOT want the component to appear, it will NOT display it, otherwise is WILL display it.

Erskine answered 31/12, 2021 at 9:32 Comment(0)
S
0

Visibility handler reusable approach

// VisibilityHandler.js
import { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";

export const VisibilityHandler = ({children, notAllowedRoute}) => {

    const location = useLocation();
    const [isVisiable, setIsVisiable] = useState(null);

    const notEmtpy = arr => arr.some( el => el === null );
    const allowed = ( arr ) => {
        let res = notEmtpy(arr.map((path) => location.pathname.match(path) ));
        if(!res) return null
        return true
    }

    useEffect( ()=> {
        setIsVisiable(allowed(notAllowedRoute));
    }, [location, window.location])

    return (
        <>
          { isVisiable && children }
        </>
    )
}
// App.js
<VisibilityHandler notAllowedRoute={["dont-show-on-this-route", "/another-route"]}>
    <h1>Your content goes here</h1>
</VisibilityHandler>
Somewise answered 7/3, 2023 at 13:1 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.