I'm trying to enable client-side routing using react-router
inside an embedded shopify app. I used this guidline from Shopify but I don't clearly understand what should be in a ./Routes
file. I tried to search for similar questions on Stack overflow or blogs, but information there is a bit outdated.
My code: App.jsx:
import {
ApolloClient,
ApolloProvider,
HttpLink,
InMemoryCache,
} from "@apollo/client";
import {
Provider as AppBridgeProvider,
useAppBridge,
} from "@shopify/app-bridge-react";
import { authenticatedFetch } from "@shopify/app-bridge-utils";
import { Redirect } from "@shopify/app-bridge/actions";
import { AppProvider as PolarisProvider } from "@shopify/polaris";
import translations from "@shopify/polaris/locales/en.json";
import "@shopify/polaris/build/esm/styles.css";
import {useMemo} from 'react';
import {useLocation, useNavigate, BrowserRouter, RouterProvider} from 'react-router-dom';
import { HomePage } from "./components/HomePage";
import Routes from './Routes';
function App() {
const navigate = useNavigate();
const location = useLocation();
const history = useMemo(
() => ({replace: (path) => navigate(path, {replace: true})}),
[navigate],
);
console.log(new URLSearchParams(document.location.search).get("host"))
const router = useMemo(
() => ({
location,
history,
}),
[location, history],
);
return (
<PolarisProvider i18n={translations}>
<AppBridgeProvider
config={{
apiKey: process.env.SHOPIFY_API_KEY,
host: new URLSearchParams(document.location.search).get("host"),
forceRedirect: true,
}}
router={router}
>
<MyProvider>
<Routes />
</MyProvider>
</AppBridgeProvider>
</PolarisProvider>
);
}
function MyProvider({ children }) {
const app = useAppBridge();
const client = new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({
credentials: "include",
fetch: userLoggedInFetch(app),
}),
});
return <ApolloProvider client={client}>{children}</ApolloProvider>;
}
export function userLoggedInFetch(app) {
const fetchFunction = authenticatedFetch(app);
return async (uri, options) => {
const response = await fetchFunction(uri, options);
if (
response.headers.get("X-Shopify-API-Request-Failure-Reauthorize") === "1"
) {
const authUrlHeader = response.headers.get(
"X-Shopify-API-Request-Failure-Reauthorize-Url"
);
const redirect = Redirect.create(app);
redirect.dispatch(Redirect.Action.APP, authUrlHeader || `/auth`);
return null;
}
return response;
};
}
export default function AppWrapper() {
return (
<BrowserRouter>
<App />
</BrowserRouter>
);
}
./Routes.jsx
import React from 'react';
import {createBrowserRouter} from "react-router-dom";
import { HomePage } from './components/HomePage';
import { Instructions } from './components/Instructions';
export default function Routes() {
const routes = createBrowserRouter([
{
path: "/",
element: <HomePage />,
},
{
path: "/statistics",
element: <HomePage />,
},
{
path: "/integration",
element: <Instructions />,
}
]);
return routes
}
Any help is massively appreciated:-)
I think the important note from the docs is this:
If you are using React Router, ensure that the Provider is a child of the router component.
Code:
export function MyApp() { const config = { apiKey: '12345', host: host }; const navigate = useNavigate(); const location = useLocation(); const history = useMemo( () => ({ replace: (path) => navigate(path, { replace: true })}), [navigate], ); const router = useMemo( () => ({ location, history, }), [location, history], ); return ( <Provider config={config} router={router} > <Routes /> </Provider> ); } export default function AppWrapper() { return ( <BrowserRouter> // <-- Router wraps App <App /> </BrowserRouter> ); }
This is because the App
component necessarily needs a routing context provided to it from higher in the ReactTree so it can use the useNavigate
and useLocation
hook and proxy a history
object to the navigate
function.
At this point the Routes
component can be any React component that renders some routes to be matched and rendered. For your case you can't use createBrowserRouter
and define the router below the App
component that is trying to consume it. Use the older BrowserRouter
as in the AppWrapper
example above, and Routes
is a component that renders the routes you want.
Example
import React from 'react';
import { useRoutes } from "react-router-dom";
import { HomePage } from './components/HomePage';
import { Instructions } from './components/Instructions';
export default function Routes() {
const routes = useRoutes([
{ path: "/", element: <HomePage /> },
{ path: "/statistics", element: <HomePage /> },
{ path: "/integration", element: <Instructions /> }.
]);
return routes;
}
...
...
import Routes from './Routes';
function App() {
const navigate = useNavigate();
const location = useLocation();
const history = useMemo(
() => ({replace: (path) => navigate(path, {replace: true})}),
[navigate],
);
const router = useMemo(
() => ({
location,
history,
}),
[location, history],
);
return (
<PolarisProvider i18n={translations}>
<AppBridgeProvider
config={{
apiKey: process.env.SHOPIFY_API_KEY,
host: new URLSearchParams(document.location.search).get("host"),
forceRedirect: true,
}}
router={router}
>
<MyProvider>
<Routes />
</MyProvider>
</AppBridgeProvider>
</PolarisProvider>
);
}
If you want to use the new react-router-dom@6.4
Data APIs then the following refactor might work for you. Create the new data-connected BrowserRouter
but refactor the routes such that App
is rendered as a layout route ( ie it is rendered on a Route
and itself renders an Outlet
for the nested routes to render their content into ).
Example:
App
...
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
...
function App() {
const navigate = useNavigate();
const location = useLocation();
const history = useMemo(
() => ({replace: (path) => navigate(path, {replace: true})}),
[navigate],
);
const router = useMemo(
() => ({
location,
history,
}),
[location, history],
);
return (
<PolarisProvider i18n={translations}>
<AppBridgeProvider
config={{
apiKey: process.env.SHOPIFY_API_KEY,
host: new URLSearchParams(document.location.search).get("host"),
forceRedirect: true,
}}
router={router}
>
<MyProvider>
<Outlet />
</MyProvider>
</AppBridgeProvider>
</PolarisProvider>
);
}
export default App;
Create the Router
.
import React from 'react';
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import App from './path/to/App';
import { HomePage } from './components/HomePage';
import { Instructions } from './components/Instructions';
const router = createBrowserRouter([
{
path="/",
element={<App />}
children: [
{
index: true,
element: <HomePage />,
},
{
path: "/statistics",
element: <HomePage />,
},
{
path: "/integration",
element: <Instructions />,
}
],
},
]);
export default function Router() {
return <RouterProvider router={router} />;
}
...
Import and render the router now:
import Router from '..path/to/Router';
...
<Router />
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.