History: Where did `basename` go? [v5]

Created on 1 Jul 2020  路  26Comments  路  Source: ReactTraining/history

I used to create a new history with a basename, this does not seem to be an option anymore.

So how do I set a basename in History v5?

Most helpful comment

Maybe I've been "doing it wrong" this whole time, but my React app sits on a subpath of a larger app, and so I used basename to define that (e.g. https://mysite.com/ui/ where ui is the root of the React app.

I define a simple custom history object:
const customHistory = createBrowserHistory({basename: '/ui'})

...which I then pass into:

<Router history={customHistory}>

I do this because I want access to history.push outside of the context of React Router (and in fact, I use that history.push method all the time).

So without the basename argument, I don't see how to make my configuration work with history v5.

Is there a "better" way of handling what I need to do? I understand there is a useHistory hook exposed from react-router-dom now, but I'm currently using my history.push method in places outside of a React component (e.g. in various Redux state files, etc).

So am I just stuck on history 4.x forever? Is there something I'm missing?

Thanks!

All 26 comments

According to the summary on the pull request, basename support was removed in v5:

https://github.com/ReactTraining/history/pull/751

According to the summary on the pull request, basename support was removed in v5:

751

馃槩

Should probably be mentioned in the "Breaking changes" section of the release.

Maybe I've been "doing it wrong" this whole time, but my React app sits on a subpath of a larger app, and so I used basename to define that (e.g. https://mysite.com/ui/ where ui is the root of the React app.

I define a simple custom history object:
const customHistory = createBrowserHistory({basename: '/ui'})

...which I then pass into:

<Router history={customHistory}>

I do this because I want access to history.push outside of the context of React Router (and in fact, I use that history.push method all the time).

So without the basename argument, I don't see how to make my configuration work with history v5.

Is there a "better" way of handling what I need to do? I understand there is a useHistory hook exposed from react-router-dom now, but I'm currently using my history.push method in places outside of a React component (e.g. in various Redux state files, etc).

So am I just stuck on history 4.x forever? Is there something I'm missing?

Thanks!

@bmueller-sykes In the same boat, so following along.

@bmueller-sykes exactly like you, I think there should be ways to let us using history out of react context and there should be basename option to let us make our app on a subpath

thank you for opening this issue

now is there any option? what should we do?

@MHA15 I don't know what our options are. Was kind of hoping @mjackson or some other maintainer would weigh in. It doesn't seem like our use-case is all that bizarre.

We also have infrastructure over createBrowserHistory and I find the removal of the API without any documentation or a way around it is really odd.

Can some maintainer please provide a W/A or are we supposed to wrap and add the basename to every route?
This is critical for us working with a reverse proxy and is also very convenient.

FWIW: It looks like React Router 6 will have a mechanism for basenames:

https://github.com/ReactTraining/react-router/blob/dev/docs/advanced-guides/migrating-5-to-6.md#move-basename-from-router-to-routes

Edit: I get that this isn't a fix for everyone.

@StringEpsilon Hmm...I'm not sure that resolves the issue, though, right? If you want to do a history.push action outside of a route, then that history object, (under RRv6 and history v5) is unaware of the base name, and therefore would potentially execute the wrong route change.

I agree with @bmueller-sykes, for now we will not update our history, but I'm hoping we wont get stuck on v4.
Im also wondering why the removal of that API. I doubt it actually hurt the implementation and I see more than a few of us were using it and relying on it in our infra. For my group, this will be a big breaking change.

It looks like React Router 6 will have a mechanism for basenames:

https://github.com/ReactTraining/react-router/blob/dev/docs/advanced-guides/migrating-5-to-6.md#move-basename-from-router-to-routes

I think that history must work without react router or react , It could to be used outside of react mechanism

I'd be happy to PR adding basename back, but I'd love to hear from a maintainer as to why it was removed before I go through that effort.

The (undocumented) removal of basename has caused us a bunch of trouble. I can't find any reason given, transition guide, depreciation warnings etc. It also looks like after around a month, there has been no response to anyone looking for more information?

Looking at the new basename prop in react-router, it looks like we're going to have to find every place in our code that uses the history ref to navigate programmatically and make them aware of the basename so they go to the correct location? (Maybe we'll write a wrapper for history to intercept .push() and auto-prepend the basename?)

I should add: Thank-you very much for the free software and the support that goes into it, it's just that the way this change has been handled has been a bit frustrating for some of us

Maybe I'm doing something wrong, but in react-router v5 I can't work out how to pass in my own history instance and a basename at the same time.

Looks like basename is a prop on BrowserRouter (which you cannot pass a history instance to) but it's not available on Router (which you can pass a history instance to).

In v5 the best I can come up with to use history from a saga/something that isn't inside a component, while also having a basename, is to use a BrowserRouter and to create a functional component that uses the useHistory hook to get access to history and save it as something like window._history to make it globally available.

import { useEffect } from 'react';
import { useHistory } from 'react-router-dom';

// include this somewhere near the top of your component structure, but below <BrowserRouter/>
export default function HistoryRefSaver() {
  let history = useHistory();

  useEffect(() => {
    window._history = history;
  });

  return null;
}

Then replace my current src/history.js with something like:

function attemptHistoryAction(callback) {
  if (!window._history) {
    console.error('history ref missing');
    return;
  }

  if (!callback) {
    console.error('callback missing');
    return;
  }

  callback(window._history);
}

export default {
  push: route => attemptHistoryAction(history => history.push(route)),
  replace: route => attemptHistoryAction(history => history.replace(route)),
  goBack: () => attemptHistoryAction(history => history.goBack()),
};

Has anyone else worked out a better way to do this?

NOTE: initially based on other's comments I thought this history ref would not be basename aware, but upon testing it seems that it is

@cinnabarcaracal that's a clever way of solving this issue, but it sure doesn't seem like it's the way this is "supposed" to be used. And, yeah, I should have mentioned more clearly that I use my independent history object all the time in my redux-saga files, which I very deliberately keep out of React components. So it still begs the question of how RRv6 and historyv5 allow for route changes that sit outside a React component.

...and +1 about the whole free software thing. (-;

in the same situation, I will be stuck in version 4

This also breaks Sentry integration: https://docs.sentry.io/platforms/javascript/guides/react/integrations/react-router/#react-router-v4v5

The above workaround would work, but ideally you'd want to set Sentry up before rendering anything.

I'm also affected by this change in several projects.

Probably doesn't cover all use cases, but I did a quick and dirty workaround for a small project I'm working on, feel free to modify if necessary.

Maybe it will be of use to some, at least for an idea how to work around the issue for now.

Create file utils/history.js

import {createBrowserHistory} from "history";

function appendBaseName (basename, to, state, callback)  {
    if (typeof to === 'string') {
        to = basename + to;
    }
    if (typeof to === 'object' && to.pathname) {
        to.pathname = basename + to.pathname;
    }
    if (state !== undefined && state.pathname) {
        to.pathname = basename + state.pathname;
    }

    return callback(to, state);
}

export default function createBrowserHistoryWithBasename(basename = '/') {
    let history = createBrowserHistory();
    history.basename = basename;

    const push = history.push;
    const replace = history.replace;
    history.push = (to, state = undefined) => appendBaseName(basename, to, state, push);
    history.replace = (to, state = undefined) => appendBaseName(basename, to, state, replace);

    return history;
};

Put the following where you import history API

import createBrowserHistoryWithBasename from "./utils/history";

let history = createBrowserHistoryWithBasename('/your/base/path'); // Replace argument with your basename

...

if we use CRA and we set <base herf="/path" /> inpublic/index.html , it is be deal?

I think this should work, I don't if it was added later on BrowserRouter has basename as a prop for the purpose.

@mukuljainx The issue is that a valid use-case is to use the history object outside the context of a React component, even within a React application, such as within a Redux state file, or just a plain Javascript file. Not being able to set basename on the history object itself means you cannot do history-based routing outside the context of a React component.

My Solution
public/manifest.json:

 {
    "start_url": "/subfolder/build/",
 }

package.json:

{
  "homepage": "/subfolder/build/",
 }

Adding <base href="%PUBLIC_URL%/"> to the public/index.html

in my app.tsx/app.jsx(for all JS users):

import { BrowserRouter } from "react-router-dom";

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter basename="/subfolder/build/">
      <App />
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

Advantages

  • no additional packages
  • easy to use
  • reproducible

My Version

  • "@types/react": "^17.0.2",

    - "@types/react-dom": "^17.0.1",

Just additional info

The compiling looks like this:

PS C:\Users\ruper\Documents\Code\ReactTypescript\myapp> npm run build

> [email protected] build C:\Users\ruper\Documents\Code\ReactTypescript\myapp
> react-scripts build

Creating an optimized production build...
Compiled with warnings.

src\assets\images\index.tsx
  Line 22:10:  Embedded <object> elements must have alternative text by providing inner text, aria-label or aria-labelledby props  jsx-a11y/alt-text

src\component\Home.tsx
  Line 3:3:  'RefObject' is defined but never used  @typescript-eslint/no-unused-vars
  Line 4:3:  'useEffect' is defined but never used  @typescript-eslint/no-unused-vars
  Line 5:3:  'useRef' is defined but never used     @typescript-eslint/no-unused-vars
  Line 6:3:  'createRef' is defined but never used  @typescript-eslint/no-unused-vars

src\component\Profile\Profile_Rupert.tsx
  Line 83:17:  Anchors must have content and the content must be accessible by a screen reader  jsx-a11y/anchor-has-content

Search for the keywords to learn more about each warning.
To ignore, add // eslint-disable-next-line to the line before.

File sizes after gzip:

  109.68 KB (+147 B)  build\static\js\2.1b5b618e.chunk.js
  29.2 KB             build\static\css\main.048ad125.chunk.css
  5.24 KB (-22 B)     build\static\js\main.e7a21f6b.chunk.js
  1.59 KB             build\static\js\3.c64630cc.chunk.js
  1.18 KB             build\static\js\runtime-main.ba8aaa23.js

The project was built assuming it is hosted at /subfolder/build/.
You can control this with the homepage field in your package.json.

The build folder is ready to be deployed.

Find out more about deployment here:

  https://cra.link/deployment

PS C:\Users\ruper\Documents\Code\ReactTypescript\myapp>

Here is the new baseurl, extracted from compiling log:
The project was built assuming it is hosted at /subfolder/build/.

Following up on this. Any more-ideal fixes? We manually set the window state to navigate to page fragments and tabs. We rely on history.location.pathname to include our basename.

@bmueller-sykes what about using history: v4.10.1 with the latest react-router-dom:5 as react-router-dom v5 as still has ^4.9.0 as a dependency so they should work well, I encountered this problem recently, needed a history object which is shared between apps containing their own react-routers. So far everything's working properly even with base prefix paths.

@mukuljainx that's exactly what I've been doing

Was this page helpful?
0 / 5 - 0 ratings

Related issues

wssgcg1213 picture wssgcg1213  路  6Comments

clayrisser picture clayrisser  路  4Comments

sidoruk-sv picture sidoruk-sv  路  3Comments

johnjesse picture johnjesse  路  6Comments

dehghani-mehdi picture dehghani-mehdi  路  6Comments