remix-run / history

Manage session history with JavaScript
MIT License
8.28k stars 961 forks source link

How to use in an iframe? #915

Closed vfonic closed 2 years ago

vfonic commented 2 years ago

I've searched and read several other issues on the same topic, but still can't figure out what's wrong in my code.

I'm loading my react app in an iframe.

I'm trying to use history to navigate to a new route (after successful ajax record create call):

// ...
onSuccess: (id) => { // react-query callback
  console.log({ id }); // this gets printed
  history.push(`/documents/${id}`);
}
// ...

This...does nothing.

Here's what I tried:

import { createBrowserHistory } from 'history';

// outside of any react component
const history = createBrowserHistory();

// react components

Also this:

import history from 'history/browser';

// react components

And this:

import { createBrowserHistory } from 'history';

const history = createBrowserHistory({
  window: iframe.contentWindow
});

// react components

How can I make this work? What am I doing wrong?

vfonic commented 2 years ago

Nevermind, the route does execute, but the component/element for the route doesn't get loaded for some reason. It was difficult to figure this out because in devtools Elements tab, the <iframe src doesn't change when the route updates.

The only way to figure this out was to go to Console tab and select app-iframe from the dropdown (that originally says top) and type window.location.href

vfonic commented 2 years ago

Ok, that doesn't work 100%.

If I set the <iframe src=... to /slideshows/123, it will render SlideshowShow. However, if I load <iframe src=... where src is /, it will render SlideshowsIndex (as expected), but then it won't navigate to SlideshowShow on history.push.

Here's my root component:

import React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import SlideshowShow from '../apps/iq_slider/SlideshowShow/SlideshowShow';
import SlideshowsIndex from '../apps/iq_slider/SlideshowsIndex';
import { renderApp } from '../layout/app';
import { createBrowserHistory } from 'history';

const history = createBrowserHistory();

const IqSlider: React.FC = () => (
  <BrowserRouter>
    <Routes>
      <Route path="/slideshows/:id" element={<SlideshowShow />} />
      <Route path="/" element={<SlideshowsIndex />} />
    </Routes>
  </BrowserRouter>
);

renderApp(IqSlider);

Here's the renderApp function:

export const renderApp = (Component: React.FC): void => {
  document.addEventListener('DOMContentLoaded', (): void => {
    const node = document.getElementById('mount-app');
    const appData = { ...node?.dataset } as ProviderProps;
    if (appData == null) {
      console.error('No App data found!');
      return;
    }

    ReactDOM.render(
      <App {...appData}>
        <Component />
      </App>,
      node,
    );
  });
};

App component is just setting some providers:

const App: React.FC<ProviderProps> = (props) => (
  <QueryClientProvider client={queryClient}>
    <AppProvider i18n={enTranslations}>
      <Page>{props.children}</Page>
    </AppProvider>
  </QueryClientProvider>
);

What am I missing? Thanks!

EDIT: Please note that I don't render <Outlet /> component anywhere. I also don't have any nested routes so it doesn't seem to me that I need to render Outlet?

vfonic commented 2 years ago

Here's what I discovered:

These don't work:

history.push(`/slideshows/${id}`, { id });
history.replace(`/slideshows/${id}`);
history.go(0);

These work:

<>
  <Link to="/">Home</Link>
  <Link to={`/slideshows/${id}`}>{name}</Link>
</>
vfonic commented 2 years ago

I figured it out. Since react-router v6, the correct approach is to use

const navigate = useNavigate();
navigate('/slideshows');