Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Where did basename go? [v5] #810

Closed
madsmadsen opened this issue Jul 1, 2020 · 44 comments
Closed

Where did basename go? [v5] #810

madsmadsen opened this issue Jul 1, 2020 · 44 comments

Comments

@madsmadsen
Copy link

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?

@StringEpsilon
Copy link

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

#751

@madsmadsen
Copy link
Author

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.

@bmueller-sykes
Copy link

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!

@pgib
Copy link

pgib commented Jul 11, 2020

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

@mh-alahdadian
Copy link

@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?

@bmueller-sykes
Copy link

@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.

@Jony-Y
Copy link

Jony-Y commented Jul 28, 2020

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.

@StringEpsilon
Copy link

StringEpsilon commented Jul 28, 2020

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.

@bmueller-sykes
Copy link

@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.

@Jony-Y
Copy link

Jony-Y commented Jul 29, 2020

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.

@mh-alahdadian
Copy link

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

@drewloomer
Copy link

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.

@cinnabarcaracal
Copy link

cinnabarcaracal commented Aug 26, 2020

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

@cinnabarcaracal
Copy link

cinnabarcaracal commented Aug 26, 2020

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

@bmueller-sykes
Copy link

bmueller-sykes commented Aug 26, 2020

@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. (-;

@jucian0
Copy link

jucian0 commented Aug 27, 2020

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

@StephanBijzitter
Copy link

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.

@martin-strobel
Copy link

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

@gudorian
Copy link

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

...

@wildfrontend
Copy link

wildfrontend commented Jan 13, 2021

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

@mukuljainx
Copy link

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

@bmueller-sykes
Copy link

@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.

@ru4ert
Copy link

ru4ert commented Mar 8, 2021

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/.

@foobarnes
Copy link

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.

@mukuljainx
Copy link

mukuljainx commented Mar 24, 2021

@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.

@bmueller-sykes
Copy link

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

@mjackson
Copy link
Member

The basename functionality was removed from this package primarily to save on size. History v5 is about 50% smaller than v4, and a lot of that code had to do with supporting the basename feature; both stripping the basename from incoming pathnames when popstate events are received and adding the basename to resolve relative paths in push() and replace().

If you're using React Router, this won't affect you because React Router v6 includes support for basenames both when matching routes and when navigating (i.e. when React Router calls out to the history library, it already knows the full pathname to use for navigation, so it doesn't rely on history's basename feature). More details are in the v5 => v6 migration guide.

If you aren't using React Router, you'll have to construct the full pathnames yourself before sending them to the history library for navigation. This could be as simple as:

let basename = '/my-basename';

function push(url) {
  history.push(basename + url);
}

push('/some/relative/pathname');

@bmueller-sykes
Copy link

@mjackson If I'm reading your reply correctly, it means there's no way to solve my issue above until RRv6 comes along (it's not out yet, right? the latest version is 5.2 on Github), is that correct? To recap, I've got a custom history object, which I define like this:

const customHistory = createBrowserHistory({basename: '/path-to-my-app'})

...which I then pass into:

<Router history={customHistory}>

...but I also use completely outside the context of RR (like inside redux-saga functions, etc), with a simple function like this:

export const navigate = (url: string) => {
  customHistory.push(url);
};

So you're saying the new approach is something like (apologies for pseudo-code):

const rootPath = '/path-to-my-app';
const customHistory = createBrowserHistory();
<Router history={customHistory} root={rootPath}>

export const navigate = (url: string) => {
  customHistory.push(rootPath + url);
};

@pshrmn
Copy link
Contributor

pshrmn commented Apr 21, 2021

@bmueller-sykes, there is a beta release of React Router v6; you can see the source code for that in the dev branch. As for history, you should still be using v4 with React Router v5.

@mjackson
Copy link
Member

@bmueller-sykes We are moving away from the idea of using your own custom history object in RR v6. If you need basename support, you can use <Routes basename>. If you need to know when the location changes, you can useLocation(). In general, the idea is to use React Router's API directly instead of calling methods on your own history instance.

@bmueller-sykes
Copy link

@pshrmn I am indeed using v4 with RRv5. (-;

@mjackson will we be able to access RR's API from outside React components? e.g. from within a redux-saga state management file? I've started using useNavigate, but of course that can only be used within the context of a React component.

Thanks to you both for replying!

@mjackson
Copy link
Member

will we be able to access RR's API from outside React components?

Probably not, no. React Router is a React library, so it's supposed to be used in the context of React.

@bmueller-sykes
Copy link

I mean...that's completely reasonable, but then we're left with using two different history objects for app navigation within the react context and outside it, right? Or am I missing something? (I might be missing something.)

@cinnabarcaracal
Copy link

cinnabarcaracal commented Apr 21, 2021

@mjackson that is a shame. Redux-Saga is not exactly a niche library within the React ecosystem (and it's not just sagas, it affects anything that is not directly within a component). I guess we will continue to smuggle a reference to the history object into the window object as a workaround

@jasperkuperus
Copy link

@mjackson that is a shame. Redux-Saga is not exactly a niche library within the React ecosystem (and it's not just sagas, it affects anything that is not directly within a component). I guess we will continue to smuggle a reference to the history object into the window object as a workaround

Unfortunately I must agree with @cinnabarcaracal here. I do see the train of thought that React Router is made for React. But indeed, redux saga is very common component in people's React stack.

@mjackson
Copy link
Member

mjackson commented Apr 23, 2021

The goal with history v5 is to make this library just an implementation detail of React Router v6. Nowhere in the RR v6 API do you get a handle on the history object. It's completely hidden.

I understand that may be frustrating to some, but we are carrying around a lot of extra weight in history v4 that just isn't needed in v5 (basenames were a big culprit), and shedding 50% size here makes a huge difference in the overall size of a React Router app. I think the vast majority of RR users will appreciate the size savings in v6.

That being said, the most common use case that I can think of for using history with redux-saga is for navigation. i.e. someone clicks a link/submits a form, you show a spinner while it's validating/submitting, then you navigate to the next page. Even though you can't use the history object directly, this is pretty easy to do with the RR v6 API:

function MyPage() {
  let navigate = useNavigate();

  function handleSubmit(event) {
    // dispatch your Redux stuff here and pass along the navigate()
    // function so you can call it when you're done
    dispatchFormSubmit(event.currentTarget, navigate);
  }

  return (
    <div>
      <form onSubmit={handleSubmit}>...</form>
    </div>
  );
}

One other nice thing about treating this library as an implementation detail is that someday it will hopefully just go away and you won't even need to care. There is a lot of work being done right now on a new window.appHistory API that will someday hopefully make window.history (and this library) obsolete. When that day comes, RR v6 will give you an <AppRouter> component in a minor feature release (not a major breaking release) and you'll be able to upgrade painlessly.

Anyway, like I said I know this is going to require some work but I just wanted y'all to know that we are doing it for a reason and hopefully adapting to this kind of architecture will better prepare you for the future.

@bmueller-sykes
Copy link

@mjackson thanks for the thoughtful reply. Truth be told, I hate navigating inside redux saga (if for no other reason than it's too far removed from the components), but for a while there was no other place to really handle complex business logic within a React app--eg if you needed to do a form post, and then conditionally post or get more data based on the reply, redux saga was your best bet. Now that async await is finally mature enough to use, I've been using that as often as I can, but holy cats there's a lot of legacy code in redux saga that may take years to convert over.

But yes, passing down a navigate object might be the most reasonable approach so long as redux saga is around, at least for me.

@NicholasGWK
Copy link

NicholasGWK commented May 6, 2021

This is more of a RR6 question but is related to history, if in RR6 you can't access the history object how do you recommend setting up microfrontends if multiple react apps need access to push / the current route but maybe aren't rendered in the same tree?

@abinasp
Copy link

abinasp commented Jun 20, 2021

#810 (comment)

is this working for basename?
any solution for this, like how we can add basename in createbrowserhistory

@idolize
Copy link

idolize commented Jun 21, 2021

Hey @abinasp you're probably better off using history@4 instead of v5 for now if you're using basename - v5 is more designed for the yet-unreleased react-router@6. See #810 (comment)

@lbassani12
Copy link

lbassani12 commented Sep 15, 2021

@mjackson is it gonna be possible in RRv6 to dinamically change the basename in <Routes> component?

My use case is a simple language and region separation of my app depending on the IP of the user, then, depending on a selector of languages, I would like to be able to change the basename dinamically and start navigation from that new basename.

Thanks in advance for the answer!

@steinarb
Copy link

steinarb commented Sep 3, 2022

This broke things for me now when and upgrade to react 18 and router v6 forced me to change from connected-react-router to redux-first-history. Setting the basename in rr v6 doesn't help me.

My app needs to figure out its webcontext path, which may be "/oldalbum" or "/" (if called through a reverse proxy) and it previously set this as the basename of the history.

I used connected-react-router to do programmatic navigation with redux actions from swipe actions.

But connected-react-router doesn't work with react 18 and react router 6 so I had to switch to redux-first-history.

At first the router paths didn't work at all, but then I figured out where react router v6 sets its basename and things started to work when clicking on links.

But when I tried swipe navigation the "/oldalbum" prefix was gone.

So I compared the path in LOCATION_CHANGE redux actions in the pre react v18/rr v6 version of the app and in the post react v18/rr v6 version.

In the pre react v18/rr v6 version both LOCATION_CHANGE events created by react router navigation, and LOCATION_CHANGE sent with push() lacks the "/oldabum" prefix.

In the post react v18/rr v6 version LOCATION_CHANGE events created by router navigation has the "/oldalbum" prefix, and the ones sent with push() lacks "/oldalbum", so they navigate to a non-existing page and 404s.

I thought redux-first-history was to blame, but it looks like this change to history was the cause.

Unsure how to proceed with this? Maybe lock history on v4 forever?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests