I am using Next.js, React18, TypeScript.
When I ran build, I got an error.
"Build error occurred"
"Error: Export encountered errors on following paths:"
/CallCenter/Admin
The cause of the error I believe is that I am contiguring routing under the pages folder.
I am using the materialUI ListItem~ListButton to prepare The side menu is prepared and routing is applied to it.
We use "outlets" of React-router-dom for the menu component to create a common layout.
Is it not possible to set routing under pages?
Please let me know your solution.
Thank you.
This is the folder structure.
src
-components
-Menu
-Atoms
-Layout
CallCenter.tsx
Admin.tsx
-pages
-Callcenter
-Admin
index.tsx(AdminRoot)
index.tsx(CallCenterRoot)
-router
CallCenterRouter.tsx
AdminRouter.tsx
This is components/Menu/CallCenter.tsx CODE
export default function MenuCallCenter () {
return (
<RouterCallCenter> // router setting
<MenuLayout.Menu MenuArr={newMenuArry}/>
</RouterCallCenter>
);
}
This is components/Menu/Admin.tsx CODE
export default function MenuAdmin() {
return (
<MenuLayout.Menu MenuArr={MenuEmployeeArr}/>
);
};
This is pages/Callcenter/Admin/index.tsx CODE
export const Home = () => {
return (
<div>
aaa
</div>
)
};
export default Home;
This is pages/Callcenter/index.tsx CODE
import { RouterAdmin } from '../../../router/Admin';
export const HomeAdmin = () => {
return (
<RouterAdmin/>
)
};
export default HomeAdmin;
This is router/CallCenterRouter.tsx CODE
export const RouterCallCenter = (props) => {
const {children} = props;
const router = useRouter();
const fromInfoFlg = router.query.fromInfoFlg;
return (
<BrowserRouter>
<Styledpage>
<Routes>
<Route path='/CallCenter' element={<MenuCallCenter/>}>
<Route path = '/CallCenter' element={fromInfoFlg == '1' ? <Navigate to= '/CallCenter/TodoList' replace/> : <Info/>}/>
<Route path = "/CallCenter/TodoList" element={<TodoList />}/>
</Route>
</Routes>
{ children }
</Styledpage>
</BrowserRouter>
);
};
This is router/AdminRouter.tsx CODE
export const RouterAdmin = (props) => {
const {children} = props;
return (
<BrowserRouter>
<Styledpage>
<Routes>
<Route path='/CallCenter/Admin' element={<MenuAdmin/>}>
<Route path = "/CallCenter/Admin/Order" element={<AdminOrder />}/>
<Route path = "/CallCenter/Admin/History" element={<AdminOrderHistory />}/>
</Route>
</Routes>
{ children }
</Styledpage>
</BrowserRouter>
);
};
Related
I am using #googlemaps/react-wrapper for my maps, one of them is using the heatmap library, and another one is not, although they are on different pages it still crashes when the user navigates between pages
Loader must not be called again with different options. {"apiKey":"***","id":"__googleMapsScriptId","libraries":[],"url":"https://maps.googleapis.com/maps/api/js"} !== {"apiKey":"***","id":"__googleMapsScriptId","libraries":["visualization"],"url":"https://maps.googleapis.com/maps/api/js"}
import { Wrapper } from "#googlemaps/react-wrapper";
import { FC } from "react";
import { Route, Switch } from "react-router";
export const Map: FC = () => {
return (
<Switch>
<Route path="/page1" exact>
<Wrapper apiKey={MAP_API_KEY} libraries={["visualization"]}>
<MapComponent />
</Wrapper>
</Route>
<Route path="/page2" exact>
<Wrapper apiKey={MAP_API_KEY}>
<MapComponent />
</Wrapper>
</Route>
</Switch>
);
};
MapComponent:
const MapComponent: FC = () => {
const [map, setMap] = useState<google.maps.Map>();
const ref = useRef(null);
useEffect(() => {
if (ref.current && !map) {
setMap(
new window.google.maps.Map(ref.current, {
center: DEFAULT_MAP_COORDINATES,
}),
);
}
}, [ref, map]);
return (
<div>
<div ref={ref} />
</div>
);
};
The issue can be solved by giving the same configuration for the google map whenever you initiate more than one instance of it in the same screen.
In your case, it's passing the same props.
import { Wrapper } from "#googlemaps/react-wrapper";
import { FC } from "react";
import { Route, Switch } from "react-router";
export const Map: FC = () => {
return (
<Switch>
<Route path="/page1" exact>
<Wrapper apiKey={MAP_API_KEY} libraries={["visualization"]}>
<MapComponent />
</Wrapper>
</Route>
<Route path="/page2" exact>
<Wrapper apiKey={MAP_API_KEY} libraries={["visualization"]}>
<MapComponent />
</Wrapper>
</Route>
</Switch>
);
};
For some others, who may be using useJsApiLoader
Just make sure that the configuration is the same in all the instances:
you can find my case explained in this issue: How to avoid 'Loader must not be called again with different options' error when using nonce in useJsApiLoader?
I am using React v17, and React Router v6+. When I load the page, the browser downloads all the js which is around 900kb, reducing the initial load time.
My routes are defined like so
const PrivateRoute = lazy(() => import("../utils/AuthenticatedRoutes"));
const Profile = lazy(() => import("../modules/Settings/User/Profile"));
const Buddies = lazy(() => import("../modules/Buddies/Buddies"));
const Buddy = lazy(() => import("../modules/Buddies/Buddy"));
const App = () => {
return (
<Suspense fallback={<Loader />}>
<Routes>
<Route path="/" element={<Landing />} />
<Route path="/profile" element={<PrivateRoute render={<Profile />} />} />
<Route path="/buddies" element={<PrivateRoute render={<Buddy />} />} />
</Routes>
</Suspense>
)
}
This is the Private Route component
const PrivateRoute = ({ render }: { render: any }) => {
const location = useLocation();
const { loggedIn } = useSelector((state: RootState) => state.userReducer);
const pathname = location.pathname;
if (!loggedIn) {
return <Navigate to={`/login?redirectTo=${pathname}&search=${location.search}`} />;
}
return render;
};
Problem:
When I load any page on the app, even the one with no element in it, the entire JS of 999kb is downloaded and I don't think lazy loading should work that way.
How can I handle this ?
This is normal. You are wrapping the whole app with Suspense, which is directly resolved by your fallback while anything under it is suspended.
How to implement Suspense with react router ?
You should defined Suspense for each route you want to have lazy loading. So when the route is called, suspense will call the fallback while anything under it is suspended.
const PrivateRoute = lazy(() => import("../utils/AuthenticatedRoutes"));
const Profile = lazy(() => import("../modules/Settings/User/Profile"));
const Buddies = lazy(() => import("../modules/Buddies/Buddies"));
const Buddy = lazy(() => import("../modules/Buddies/Buddy"));
const App = () => {
return (
<Routes>
<Route path="/" element={<Landing />} />
<Route path="/profile" element={<PrivateRoute render={<React.Suspense fallback={<Loader />}><Profile /></React.Suspense>} />} />
<Route path="/buddies" element={<PrivateRoute render={<React.Suspense fallback={<Loader />}><Buddy /></React.Suspense>} />} />
</Routes>
)
}
This is the same as the official documentation
I don't see any overt issues with the way you've implemented your code. The code splitting appears to be working.
Suggestion
I suggest refactoring the PrivateRoute component to be a layout route component instead of a wrapper component.
Example:
import { Navigate, Outlet, useLocation } from 'react-router-dom';
const PrivateRoute = () => {
const location = useLocation();
const { loggedIn } = useSelector((state: RootState) => state.userReducer);
const pathname = location.pathname;
if (!loggedIn) {
return <Navigate to={`/login?redirectTo=${pathname}&search=${location.search}`} />;
}
return <Outlet />;
};
...
const PrivateRoute = lazy(() => import("../utils/AuthenticatedRoutes"));
const Profile = lazy(() => import("../modules/Settings/User/Profile"));
const Buddies = lazy(() => import("../modules/Buddies/Buddies"));
const Buddy = lazy(() => import("../modules/Buddies/Buddy"));
const App = () => {
return (
<Suspense fallback={<Loader />}>
<Routes>
<Route path="/" element={<Landing />} />
<Route element={<PrivateRoute />}>
<Route path="/profile" element={<Profile />} />
<Route path="/buddies" element={<Buddy />} />
</Route>
</Routes>
</Suspense>
)
}
With this implementation I do see the Loader component "blip" on the screen momentarily for the first time each dynamically imported component is routed to.
I think it might be as Colin called out in a comment, that the dynamically imported components just aren't a significant portion of your overall app bundle size.
I am working on a project where I am using the strikingDash template. Here I face some issues of routing while changing the routes from URL.
auth.js
import React, { lazy, Suspense } from "react"
import { Spin } from "antd"
import { Switch, Route, Redirect } from "react-router-dom"
import AuthLayout from "../container/profile/authentication/Index"
const Login = lazy(() =>
import("../container/profile/authentication/overview/SignIn")
)
const SignUp = lazy(() =>
import("../container/profile/authentication/overview/SignUp")
)
const ForgetPassword = lazy(() =>
import("../container/profile/authentication/overview/ForgetPassword")
)
const EmailConfirmation = lazy(() =>
import("../container/profile/authentication/overview/EmailConfirmation")
)
const VerificationPage = lazy(() =>
import("../container/profile/authentication/overview/VerificationPage")
)
const NotFound = () => {
console.log("NOT FOUND")
return <Redirect to="/" />
}
const FrontendRoutes = () => {
return (
<Switch>
<Suspense
fallback={
<div className="spin">
<Spin />
</div>
}
>
<Route exact path="/verification" component={VerificationPage} />
<Route exact path="/email-confirmation" component={EmailConfirmation} />
<Route exact path="/forgetPassword" component={ForgetPassword} />
<Route exact path="/signup" component={SignUp} />
<Route exact path="/" component={Login} />
<Route component={NotFound} />
</Suspense>
</Switch>
)
}
export default AuthLayout(FrontendRoutes)
App.js
import React, { useEffect, useState } from "react";
import { hot } from "react-hot-loader/root";
import { Provider, useSelector } from "react-redux";
import { ThemeProvider } from "styled-components";
import { BrowserRouter as Router, Redirect, Route } from "react-router-dom";
import { ConfigProvider } from "antd";
import store from "./redux/store";
import Admin from "./routes/admin";
import Auth from "./routes/auth";
import "./static/css/style.css";
import config from "./config/config";
import ProtectedRoute from "./components/utilities/protectedRoute";
const { theme } = config;
const ProviderConfig = () => {
const { rtl, isLoggedIn, topMenu, darkMode } = useSelector(state => {
return {
darkMode: state.ChangeLayoutMode.data,
rtl: state.ChangeLayoutMode.rtlData,
topMenu: state.ChangeLayoutMode.topMenu,
isLoggedIn: state.Authentication.login,
};
});
const [path, setPath] = useState(window.location.pathname);
useEffect(() => {
let unmounted = false;
if (!unmounted) {
setPath(window.location.pathname);
}
// eslint-disable-next-line no-return-assign
return () => (unmounted = true);
}, [setPath]);
return (
<ConfigProvider direction={rtl ? "rtl" : "ltr"}>
<ThemeProvider theme={{ ...theme, rtl, topMenu, darkMode }}>
<Router basename={process.env.PUBLIC_URL}>
{!isLoggedIn ? <>{console.log("INSIDE PUBLIC")}<Route path="/" component={Auth} /></> : <ProtectedRoute path="/admin" component={Admin} />}
{isLoggedIn && (path === process.env.PUBLIC_URL || path === `${process.env.PUBLIC_URL}/`) && (
<Redirect to="/admin" />
)}
</Router>
</ThemeProvider>
</ConfigProvider>
);
};
function App() {
return (
<Provider store={store}>
<ProviderConfig />
</Provider>
);
}
export default hot(App);
Whenever I change the URL to another route as I described above in Frontend Routes. Then it will always print console statements like these:
INSIDE PUBLIC
NOT FOUND
INSIDE PUBLIC
NOT FOUND
Expected Behaviour: Whenever I update the URL it will render the component according to the switch case and return it back
Actual Behaviour: Whenever I update the URL it will render the component as well as the default component. I think Switch here renders multiple components, but I don't know why.
I just resolved the issue by moving the Switch Tag inside the Suspense tag in the auth.js file.
The problem should be in the order of your pages: the root path works as a collector of all the pages, you should try to add the exact keyword to the Router path. Here the reference for the differences between the different notations.
<Route exact path="/" component={Login} />
I am developing an react js app using functional components.
I am trying to reuse components in the code. I have a component Frame which has Sider and Header. I am trying to add Content component to that frame to display in the middle, but its not working though.
Frame.tsx
const Frame : React.FC = (props) => {
const [collapsed, onCollapse] = useState(false);
const Content = props.content;
console.log('Content: ',Content);
return (
<Layout>
<SideBar state={{ collapsed: [collapsed, onCollapse]}}/>
<Layout>
<HeaderBar state={{ collapsed: [collapsed, onCollapse]}}/>
{Content}
<Footer>footer</Footer>
</Layout>
</Layout>
);
}
export default Frame;
PublicRoute.tsx
interface PublicRouteProps extends RouteProps {
// tslint:disable-next-line:no-any
component: any;
isAuthorized: boolean;
content: Content;
}
const PublicRoute = (props: PublicRouteProps) => {
const { component: Component, isAuthorized, content: Dummy, ...rest } = props;
return (
<Route
{...rest}
render={(routeProps) =>
isAuthorized ? (
<Component {...routeProps}/>
) : (
<Redirect
to={{
pathname: '/login',
state: { from: routeProps.location }
}}
/>
)
}
/>
);
};
export default PublicRoute;
App.tsx
return (
<BrowserRouter>
<div>
<Switch>
<PublicRoute path="/" component={Frame} exact isAuthorized={true} content={Dummy}/>
<Route path="/login" component={NewLogin} exact isAuthorized={true}/>
</Switch>
</div>
</BrowserRouter>
);
I am not able to pass contents dynamically and I am not sure whats wrong.
Thank you for your time.
You have to pass to the component with <Content /> otherwise it won't be instantiated.
Here's a full example
import React from "react";
import "./styles.css";
function Parent({content}) {
return (
<div>
{content}
</div>
)
}
function Content() {
return (
<h1>Hello</h1>
)
}
export default function App() {
return (
<div className="App">
<Parent content={<Content/>} />
</div>
);
}
You pass the components like so: .
Try something like this:
return (
<BrowserRouter>
<div>
<Switch>
<PublicRoute path="/" component={<Frame />} exact isAuthorized={true} content={<Dummy />}/>
<Route path="/login" component={<NewLogin />} exact isAuthorized={true}/>
</Switch>
</div>
</BrowserRouter>
);
I guess we can use the createElement function in place of {content}.
{React.createElement("NewLogin");}
Please I have an issue building a multi-tenant SaaS solution. For every tenant, I want them to use a subdomain, so i can get the subdomain from the url, make a call to a REST api that returns data about that tenant.
For example,
the admin (another app entirely - admin app) creates a tenant with domain name: tenant1.
In the tenant application on my local system, I was able to go to tenant1.localhost:3000. I get the url, and get the domain name. I then make a call with the domain to get the theme of tenant (this is stored in localStorage).
Unfortunately, we deploy on k8 in my company and so I couldn't mimic this behavior. So i have been advised by the devOps team to use subdomain in the context, thereby having localhost:3000/tenant1. Remember the tenant is dynamic, so i tried this:
<BrowserRouter basename={"/:tenant"}>
<Switch>
<Route exact path="/login" name="Login" component={Login} />
<Route exact path="/set-password/:token" name="Set Password" component={SetPassword} />
<PrivateRoute path="/" name="Default Layout" component={DefaultLayout} />
</Switch>
</BrowserRouter>
The solution above however makes my url to localhost:3000/:tenant/login
Please how can i use dynamic basename in the router, so it can accept:
localhost:3000/tenant1
localhost:3000/tenant3
localhost:3000/tenant2 etc.
It can allow any, my app handles wrong domain inputted
I finally used dynamic tenant with the following code
class App extends Component {
state = {
domain: ""
}
componentWillMount () {
const { domain } = this.state;
const parsedData = window.location.pathname.split("/");
let domain = parsedData[1];
this.setState({ domain: domain })
this.props.onGetTenant(domain);
}
render () {
const { domain } = this.state;
return () {
<BrowserRouter basename={"/"+domain}>
<Switch>
<Route exact path="/login" name="Login" component={Login} />
<Route exact path="/set-password/:token" name="Set Password" component={SetPassword} />
<PrivateRoute domain={domain} path="/" name="Default Layout" component={DefaultLayout} />
</Switch>
</BrowserRouter>
}
const mapStateToProps = state => {
const { tenant} = state;
return { tenant};
};
const mapDispatchToProps = (dispatch) => {
return {
onGetTenant: bindActionCreators( tenantActions.get, dispatch)
}
};
export default connect(mapStateToProps, mapDispatchToProps)(App)
This worked for me using react >16 and react-router-dom v5
export const App = () => {
return (
<BrowserRouter>
<Switch>
<Route path="/:tenantId?" component={LayoutRoot} />
</Switch>
</BrowserRouter>
);
};
export const LayoutRoot = () => {
var { tenantId } = useParams();
//TODO: add some validation here and inform user if tenant is invalid
return (
<BrowserRouter basename={tenantId}>
<Switch>
<Route path="/login" component={LoginComponent} />
<Route path="/dashboard" component={DashboardComponent} />
</Switch>
</BrowserRouter>
);
};
You can render updates to your router's basename by using the key property. Any changes to the key value will cause the component to re-render.
Here's a code sandbox to demo:
https://codesandbox.io/s/react-router-dom-dynamic-basename-forked-hnkk0?file=/index.js
You can hover or inspect the links in the sandbox to verify that their href values are correctly updating after changing the basename. You can also see that the hrefs won't update if you remove the key property from Router.
import React, { useState } from "react";
import { render } from "react-dom";
import { BrowserRouter, Link } from "react-router-dom";
const Root = () => {
const [count, setCount] = useState(1);
const basename = `basename-${count}`;
return (
<BrowserRouter basename={basename} key={basename}>
<Link to="/link1">Link 1</Link>
<br />
<Link to="/link2">Link 2</Link>
<br />
Current basename: {basename}
<br />
<button onClick={() => setCount(count + 1)}>change basename</button>
</BrowserRouter>
);
};
render(<Root />, document.getElementById("root"));
Here's a codesandbox and the utility I wrote:
https://codesandbox.io/s/react-router-dom-dynamic-basename-xq9tj?file=/index.js
import urlJoin from 'url-join';
// it's important to have an identifier in their app
export const APP_ROOT_URL = '/my-app';
export const getBaseUrlPath = () => {
const currentPath = document.location.pathname || APP_ROOT_URL;
const startOfAppBase = currentPath.indexOf(APP_ROOT_URL);
let base = currentPath;
if (startOfAppBase !== -1) {
base = currentPath.substr(0, startOfAppBase);
}
base = urlJoin(base, APP_ROOT_URL);
return base;
};