Home » Reactjs » How to stop /#/ in browser with react-router?

How to stop /#/ in browser with react-router?

Posted by: admin November 29, 2017 Leave a comment

Questions:

Any way to prevent /#/ from showing in the browser’s address bar when using react-router? That’s with ReactJS. i.e. Clicking on links to go to a new route shows localhost:3000/#/ or
localhost:3000/#/about. Depending on the route.

Answers:

For the versions 1, 2 and 3 of react-router, the correct way to set the route to URL mapping scheme is by passing a history implementation into the history parameter of <Router>. From the histories documentation:

In a nutshell, a history knows how to listen to the browser’s address bar for changes and parses the URL into a location object that the router can use to match routes and render the correct set of components.

Versions 2 and 3

In react-router 2 and 3, your route configuration code will look something like this:

import { browserHistory } from 'react-router'
ReactDOM.render (( 
 <Router history={browserHistory} >
   ...
 </Router> 
), document.body);

Version 1

In version 1.x, you will instead use the following:

import createBrowserHistory from 'history/lib/createBrowserHistory'
ReactDOM.render (( 
  <Router history={createBrowserHistory()} >
   ...
  </Router> 
), document.body);

Source: Version 2.0 Upgrade Guide

Version 4

For the upcoming version 4 of react-router, the syntax has changed a great deal and it is required is to use BrowserRouter as the router root tag.

import BrowserRouter from 'react-router/BrowserRouter'
ReactDOM.render (( 
  <BrowserRouter>
   ...
 <BrowserRouter> 
), document.body);

Source React Router Version 4 Docs

Questions:
Answers:
Router.run(routes, Router.HistoryLocation, function (Handler) {
  React.render(<Handler/>, document.body);
});

For the current version 0.11 and forward, you need to add Router.HistoryLocation to Router.run(). <Routes> are now deprecated. See the Upgrade Guide for the 0.12.x HistoryLocation implementation.

Questions:
Answers:

If you don’t need to support IE8, you can use Browser History and react-router will use window.pushState instead of setting the hash.

How exactly to do this depends on the version of React Router that you are using:

Questions:
Answers:

You can actually use .htaccess to accomplish this. The browser normally needs the query string delimiter ? or # to determine where the query string begins and the directory paths end.
The end result we want is www.mysite.com/dir
So we need to catch the issue before the web server searches for the directory it thinks we asked for /dir.
So we place a .htaccess file in the root of the project.

    # Setting up apache options
    AddDefaultCharset utf-8
    Options +FollowSymlinks -MultiViews -Indexes
    RewriteEngine on

    # Setting up apache options (Godaddy specific)
    #DirectoryIndex index.php
    #RewriteBase /


    # Defining the rewrite rules
    RewriteCond %{SCRIPT_FILENAME} !-d
    RewriteCond %{SCRIPT_FILENAME} !-f

    RewriteRule ^.*$ ./index.html

Then you obtain the query parameters with window.location.pathname

You can then avoid using react routes if you want and just manipulate the url and browser history if you want as well.
Hope this helps someone…

Questions:
Answers:

Install the history package

npm install history --save

Next import createHistory and useBasename from the history

import { createHistory, useBasename } from 'history';
...
const history = useBasename(createHistory)({
  basename: '/root' 
});

if your app url is www.example.com/myApp, then /root should be /myApp.

pass the history variable to the Router

render((
  <Router history={history}>
    ...
  </Router>
), document.getElementById('example'));

Now for all your Link tags append a “/” in front of all the paths.

<Link to="/somewhere">somewhere</Link>

The inspiration of the solution came from React-Router Example
Which unfortunately, was not properly documented in their API.

Questions:
Answers:

Another way to handle what to display after the hash (so if you don’t use pushState !) is to create your CustomLocation and load it at ReactRouter creation.

For exemple, if you want to have hashbang url (so with #!) to comply with google specs for crawling, your can create a HashbangLocation.js file which mainly copy the original HashLocation such as :

'use strict';

var LocationActions = require('../../node_modules/react-router/lib/actions/LocationActions');
var History = require('../../node_modules/react-router/lib/History');

var _listeners = [];
var _isListening = false;
var _actionType;

function notifyChange(type) {
  if (type === LocationActions.PUSH) History.length += 1;

  var change = {
    path: HashbangLocation.getCurrentPath(),
    type: type
  };

  _listeners.forEach(function (listener) {
    listener.call(HashbangLocation, change);
  });
}

function slashToHashbang(path) {
  return "!" + path.replace(/^\//, '');
}

function ensureSlash() {

  var path = HashbangLocation.getCurrentPath();
  if (path.charAt(0) === '/') {
    return true;
  }HashbangLocation.replace('/' + path);

  return false;
}

function onHashChange() {
  if (ensureSlash()) {
    // If we don't have an _actionType then all we know is the hash
    // changed. It was probably caused by the user clicking the Back
    // button, but may have also been the Forward button or manual
    // manipulation. So just guess 'pop'.
    var curActionType = _actionType;
    _actionType = null;
    notifyChange(curActionType || LocationActions.POP);
  }
}

/**
 * A Location that uses `window.location.hash`.
 */
var HashbangLocation = {

  addChangeListener: function addChangeListener(listener) {
    _listeners.push(listener);

    // Do this BEFORE listening for hashchange.
    ensureSlash();

    if (!_isListening) {
      if (window.addEventListener) {
        window.addEventListener('hashchange', onHashChange, false);
      } else {
        window.attachEvent('onhashchange', onHashChange);
      }

      _isListening = true;
    }
  },

  removeChangeListener: function removeChangeListener(listener) {
    _listeners = _listeners.filter(function (l) {
      return l !== listener;
    });

    if (_listeners.length === 0) {
      if (window.removeEventListener) {
        window.removeEventListener('hashchange', onHashChange, false);
      } else {
        window.removeEvent('onhashchange', onHashChange);
      }

      _isListening = false;
    }
  },

  push: function push(path) {
    _actionType = LocationActions.PUSH;
    window.location.hash = slashToHashbang(path);
  },

  replace: function replace(path) {
    _actionType = LocationActions.REPLACE;
    window.location.replace(window.location.pathname + window.location.search + '#' + slashToHashbang(path));
  },

  pop: function pop() {
    _actionType = LocationActions.POP;
    History.back();
  },

  getCurrentPath: function getCurrentPath() {
    return decodeURI(
    // We can't use window.location.hash here because it's not
    // consistent across browsers - Firefox will pre-decode it!
    "/" + (window.location.href.split('#!')[1] || ''));
  },

  toString: function toString() {
    return '<HashbangLocation>';
  }

};

module.exports = HashbangLocation;

Note the slashToHashbang function.

Then you juste have to do

ReactRouter.create({location: HashbangLocation})

And that’s it 🙂