Telegram-Mini-Apps / telegram-apps

Made from scratch TypeScript packages, examples and documentation you will surely need to start developing on Telegram Mini Apps.
https://docs.telegram-mini-apps.com/
MIT License
543 stars 136 forks source link

[Feature]: react-router v6.4 Data APIs integration #423

Open izzm opened 1 month ago

izzm commented 1 month ago

Is your feature request related to a problem? Please describe.

react-router v6.4 provides new API to routers and new routes definitions. And documentation recommends to use new API https://reactrouter.com/en/main/routers/picking-a-router for a new projects.

Describe the solution you'd like

Ability to use telegram-apps BrowserNavigator along with new react-router API.

Describe alternatives you've considered

Manually show/hide back button using router, initialized by createBrowserRouter method and tracking location using useLocation hook;

const location = useLocation();
const backButton = useBackButton();
const navigate = useNavigate();

useEffect(() => {
  if (location.pathname === "/" && backButton.isVisible) {
    backButton.hide();
  } else if (location.pathname !== "/" && !backButton.isVisible) {
    backButton.show();
  }
}, [location, backButton]);

useEffect(() => {
  backButton.on("click", () => navigate(-1), true);
}, [backButton, navigate]);

Additional context

No response

heyqbnk commented 1 month ago

What is the difference with the current implementation?

https://docs.telegram-mini-apps.com/packages/telegram-apps-react-router-integration

izzm commented 1 month ago

In 6.4 react-router introduces new Data API with async data loaders and actions, component lazy loading and other stuff https://reactrouter.com/en/main/routers/picking-a-router#data-apis.

This new methods available only while initializing a router in a new way - using function call, for example createBrowserRouter and using RouterProvider component to attach router to the React component tree.

heyqbnk commented 1 month ago

But createBrowserRouter creates a BrowserRouter. We don't need it, don't we? We need a router based on a custom navigator. That's why we implemented react-router-integration

izzm commented 1 month ago

Yes, we need a custom router. With react-router-integration routes are defined in pre-6.4 style, for example (snippet from documentation):

return (
  <Router location={location} navigator={reactNavigator}>
    <Routes>
      <Route path={'/'} component={IndexPage}/>
      <Route path={'*'} element={<Navigate href={'/'}/>}/>
    </Routes>
  </Router>
);

location and reactNavigator are objects, created by react-router-integration, and it works perfectly. React router version 6.4+ provides new way to attach routes to React component tree, new versions uses RouterProvider component, which allow new features of react router to be used.

New api officially not expose base createRouter method, but it can be imported directly from '@remix-run/router' package (which is not recommended by https://github.com/remix-run/react-router/tree/main/packages/router, but this is the only way I found to implement a custom router). This method get history argument with a type History, which is similar to Navigator, but have few additional properties. location is moved to history property.

For the integration with a new react-router API react-router-integration needs to return an object, compatible with a History type.

izzm commented 1 month ago

I create a workaround for 6.4+ style routes definition:

import {
  Action,
  createRouter,
  Path,
  To,
  type History,
} from "@remix-run/router";

...

const navigator = useMemo(() => initNavigator("app-navigation-state"), []);
const [location, reactNavigator] = useIntegration(navigator);

useEffect(() => {
  navigator.attach();
  return () => navigator.detach();
}, [navigator]);

const history: History = {
  location,
  push: reactNavigator.push,
  replace: reactNavigator.replace,
  action: Action.Pop,
  createHref: reactNavigator.createHref,
  createURL: (to: To) => {
    return new URL(reactNavigator.createHref(to), window.location.toString());
  },
  encodeLocation: (to: To) => {
    return (
      reactNavigator.encodeLocation?.(to) ||
      ({ pathname: "/", search: "", hash: "" } as Path)
    );
  },
  go: reactNavigator.go,
  listen: () => {
    return () => {};
  },
};

const routes = ... // define you routes here
const router = createRouter({ routes, history }).initialize();

return (<RouterProvider router={router} />);

Maybe, it will be better to implement this in react-router-integration?

GrayYoung commented 3 days ago

listen: () => { return () => {}; },

It works, but the Back button does not respond.