steliosbl
steliosbl

Reputation: 8921

React Router redirect hash link

I have created a custom button component for my website's navbar. When the user clicks on a button, the component returns a Redirect, which takes the user to the page they selected.

export default class Button extends Component {
    constructor(props){
        super(props);
        this.state = {redirect:false};
        this._handleClick = this._handleClick.bind(this);
    }

    _handleClick(e) {
        e.stopPropagation();
        this.setState({redirect: true});
    }

    componentDidUpdate() {
        if (this.state.redirect){
            this.setState({redirect:false});
            this.props.onRedirect();
        }
    }

    render() {
        if (this.state.redirect){
            return <Redirect push to={this.props.dest}/>;
        }
        else {
            return (
                <li className="button" onClick={this._handleClick}>
                    <h5>{this.props.text}</h5>
                </li>
            );
        }
    }
}

Now, I'd like to add buttons that correspond to different sections of the same page. The simplest way I know of is to use hash links. One example of an address the button would redirect to is:

/home#description

However, React Router does not support doing this out of the box. I looked through a number of packages which add this functionality, such as react-router-hash-link and react-scrollchor. None of these however work with redirects, instead relying on Link or on custom components.

How do I go about adding this functionality to the buttons?

Upvotes: 9

Views: 10612

Answers (7)

justFatLard
justFatLard

Reputation: 526

I was trying to solve a similar but slightly different issue, I want to deprecate an old hash route in favor of a new one. The posts here helped me arrive to my eventual solution:

<Route
    exact
    path={'/thing/:id'}
    render={({
        match: {
            params: { id },
        },
    }) => (
        <Redirect
            push
            to={`/newThing/${id}`}
        />
    )}
/>

Upvotes: 0

Alex Dunlop
Alex Dunlop

Reputation: 1606

Who said React Router doesn't support this out of the box! You don't need those packages. You can redirect a hash i'll give you an example using the React-Router Route.

<Route
  exact
  path="/signup"
  render={props => {
    if (props.location.hash === "#foo")
      return <Redirect push to="signup#bar"
    return <Signup />
  }}
/>

Now your version may not have supported this now that I think about it, but let me know if this helps :) Happy coding!

Upvotes: 2

Shrishail Uttagi
Shrishail Uttagi

Reputation: 436

I was facing the same issue, I have created HOC to handle hash redirection, you can follow the below steps to achieve a hash redirection

  1. create HOC and add below code to it

fileName : hashComponent

import React, { useEffect } from 'react';

export default function hashComponent(WrappedComponent) {

  return function () {
    const { pathname, hash }=window.location;
    useEffect(() => {

      if(hash)
        window.location.href=`${pathname}${hash}`;
    }, [hash])

    return <WrappedComponent />
  }
}
  1. import your HOC in the component to which you want to handle hash URL

  2. Then add below line of code while exporting your component

    export default hashComponent(YourComponentName)

Upvotes: -1

GlyphCat
GlyphCat

Reputation: 180

One solution that I can think of is to use HOCs and hooks. The end result:

  • You'll get your app to scroll to the specified location...
  • without really needing to create custom buttons/links and...
  • without making much changes to your existing screens (Eg: HomeScreen)
  • Bonus: Users can copy, share & use URLs that will automatically scroll to the intended section


With assumption that the code below are pseudocode (they are based on my knowledge and not tested) and assuming there's a HomeScreen component, I would attempt adding <Route/>s to the <Switch/> inside the <Router/>.

<Switch>
  <Route to='/home/:section' component={HomeScreen} />
  <Route to='/home' component={HomeScreen} />
</Switch>


Then:

function withScrollToTarget(WrappedComponent) {
  class WithScroll extends React.Component {
    componentDidMount() {
      const { match: { params: { section } } } = this.props
      // Remember we had 2 <Route/>s, so if `section` is provided...
      if (section) {
        const scrollToTarget = document.getElementById(section)
        // And just in case the item was removed or there was an ID mismatch
        if (scrollToTarget) { scrollToTarget.scrollIntoView() }
      }
    }
    render() { return <WrappedComponent {...this.props} /> }
  }
  return WithScroll
}

function useScrollToTarget(section) {
  useEffect(() => {
    if (section) {
      const scrollToTarget = document.getElementById(section)
      if (scrollToTarget) { scrollToTarget.scrollIntoView() }
    }
  }, [section])
}


Usage:

<nav>
  <Link to='/home'>{'Home'}</Link>
  <Link to='/home/description'>{'Description'}</Link>
</nav>


class HomeScreen extends React.Component { /* ... */ }
export default withScrollToTarget(HomeScreen)
// or
function HomeScreen() {
  const { params: { section } } = useMatch() // from react-router-dom
  useScrollTotarget(section)
  return (
    <div>
      <h1 id='introduction'>Introduction</h1>
      <h1 id='description'>Description</h1>
    </div>
  )
}


TLDR:

  • The route for '/home/:section' must be on top of '/home'. If the opposite, every time when <Switch/> compares the current URL against to, it will evaluate to true upon reaching '/home' and never reach '/home/:section'
  • scrollIntoView() is a legit function
  • If this works for you, you should look up on how to forward refs and hoisting statics in HOCs too

Upvotes: 2

I think you should use the react-router-dom.

yarn add react-router-dom

Now update Custom Button Component like this

import React from 'react';
import { withRouter } from "react-router-dom";
class Button extends Component {
    constructor(props){
        super(props);
        this.state = {redirect:false};
        this._handleClick = this._handleClick.bind(this);
    }

    _handleClick(e) {
        e.stopPropagation();
        this.setState({redirect: true});
    }

    componentDidUpdate() {
        if (this.state.redirect){
            this.setState({redirect:false});
            //this.props.onRedirect();
            this.props.history.push('new uri');
        }
    }

    render() {
        if (this.state.redirect){
            return <Redirect push to={this.props.dest}/>;
        }
        else {
            return (
                <li className="button" onClick={this._handleClick}>
                    <h5>{this.props.text}</h5>
                </li>
            );
        }
    }
}
export default withRouter(Button);

Upvotes: 0

automasean
automasean

Reputation: 421

React-hash-link should work for your redirect use case.

You can add <HashLinkObserver /> to your component tree and it will listen for hash links and scroll accordingly rather than relying on Link or custom components.

Upvotes: 0

pureth
pureth

Reputation: 838

you could update window.location.href since it won't trigger a page refresh.

e.g.

window.location.href = '#your-anchor-tag';

Upvotes: 2

Related Questions