I have a list of options in a React component which I am mapping through to make some UI. The component is being used in QwikJS, which supports React components and uses Vite. However, when I add an icon value in my list of options Vite breaks during the build.
import { IconHome, IconMail, IconPencilPlus } from '#tabler/icons'
interface OptionProps {
id: number
text: string
icon: React.ReactElement
url?: string
action?: () => void
}
export const Command = qwikify$(() => {
...
const options: OptionProps[] = [
{
id: 1,
text: 'Home',
icon: <IconHome className='h-5 w-5' stroke={1} />,
url: '/',
action: undefined,
},
{
id: 2,
text: 'Join the waitlist',
icon: <IconPencilPlus className='h-5 w-5' stroke={1} />,
url: undefined,
action: () => {
setShowWaitlist(true)
},
},
]
...
}
I can't work out how to define the icon. Vite keeps giving me the error:
[vite] Internal server error: Cannot read properties of undefined (reading 'IconHome')
I've used the same code in Next (this is Qwik). Can anyone see what I'm doing wrong? - would like to render the icon when I map through the options, ie:
{options.map((option, i) => (
<div>
<div>{option.icon}</div>
<div>{option.text}</div>
<div>
))}
import { IconHome, IconMail, IconPencilPlus } from '#tabler/icons'
interface OptionProps {
id: number
text: string
icon: () => React.ReactElement
url?: string
action?: () => void
}
export const Command = qwikify$(() => {
...
const options: OptionProps[] = [
{
id: 1,
text: 'Home',
icon: () => <IconHome className='h-5 w-5' stroke={1} />,
url: '/',
action: undefined,
},
{
id: 2,
text: 'Join the waitlist',
icon: () => <IconPencilPlus className='h-5 w-5' stroke={1} />,
url: undefined,
action: () => {
setShowWaitlist(true)
},
},
]
...
}
{options.map((option, i) => (
<div>
<div>{option.icon()}</div>
<div>{option.text}</div>
<div>
))}
I want to redirect to "/building" when I load the root path "/".
I use v6 of react-router-dom.
This is my config.
const routes: Array<Route> = [
{
path: '/',
element: () => import('#/layout/layout'),
children: [
{
path: 'building',
element: () => import('#/pages/buildingPortfolio'),
},
{
path: 'account',
element: () => import('#/pages/manageAccount'),
children: [
{
path: 'users',
element: () => import('#/pages/manageAccount/users'),
},
{
path: 'api-keys',
element: () => import('#/pages/manageAccount/apiKeys'),
},
],
},
],
}
];
function LazyElement({ routeElement }: RouteElement) {
const LazyComponent = lazy(routeElement);
return (
<Suspense fallback={<div>loading...</div>}>
<LazyComponent />
</Suspense>
);
}
function wrapRoutesWithLazy(routes: Route[]) {
routes.forEach((route) => {
const routeElement = route.element;
route.element = (<LazyElement routeElement={routeElement} />) as any;
if (route.children) {
wrapRoutesWithLazy(route.children);
}
});
}
wrapRoutesWithLazy(routes);
export default routes;
I try to replace this
path: '/',
element: () => import('#/layout/layout'),
to
path: '/',
element: <Navigate to="/building" />,
And when return type of element is not Promise, then I skip to wrapper it use React.lazy
Oh, I solved it.
The key point is add this part in children.
{
path: '',
skipLazyLoad: true,
element: <Navigate to="building" />,
And this is the whole file.
import { lazy, ReactElement, Suspense } from 'react';
import { Navigate } from 'react-router-dom';
interface Route {
path?: string;
element: (() => Promise<any>) | ReactElement;
end?: boolean;
children?: Route[];
skipLazyLoad?: boolean;
}
interface RouteElement {
routeElement: () => Promise<any>;
}
const routes: Array<Route> = [
{
path: '/',
element: () => import('#/layout/layout'),
children: [
{
path: '',
skipLazyLoad: true,
element: <Navigate to="building" />,
},
{
path: 'building',
element: () => import('#/pages/buildingPortfolio'),
},
{
path: 'account',
element: () => import('#/pages/manageAccount'),
children: [
{
path: '',
skipLazyLoad: true,
element: <Navigate to="users" />,
},
{
path: 'users',
element: () => import('#/pages/manageAccount/users'),
},
{
path: 'api-keys',
element: () => import('#/pages/manageAccount/apiKeys'),
},
],
},
],
},
{
path: '*',
element: () => import('#/pages/notFound'),
},
];
function LazyElement({ routeElement }: RouteElement) {
const LazyComponent = lazy(routeElement);
return (
<Suspense fallback={<div>loading...</div>}>
<LazyComponent />
</Suspense>
);
}
function wrapRoutesWithLazy(routes: Route[]) {
routes.forEach((route) => {
if (route.skipLazyLoad) return;
const routeElement = route.element as () => Promise<any>;
route.element = (<LazyElement routeElement={routeElement} />) as any;
if (route.children) {
wrapRoutesWithLazy(route.children);
}
});
}
wrapRoutesWithLazy(routes);
export default routes;
I have a file called routes.js there I import components and create a routes array.
routes.js
import Country from '../src/components/country/Country';
import Countries from '../src/components/country/CountriesList';
import User from '../src/components/user/User';
import UsersList from '../src/components/user/UsersList';
export const routes = [
{
name: 'USER',
children: [
{
name: 'Create',
path: '/user-create',
component: User,
// isHidden: true,
},
{
name: 'Update',
path: '/update-user',
isHidden: true,
component: User,
},
{
name: 'View',
path: '/users',
component: UsersList,
// isHidden: true,
},
],
},
{
name: 'COUNTRY',
children: [
{
name: 'Create',
path: '/country',
component: Country,
},
{
name: 'View',
path: '/countries',
component: Countries,
},
],
},
],
},
];
Then, I want to import that routes array and render routes programmatically.
I did this.
{routes.map(
{children}=>
children.forEach({component,path} => <Route component={component} exact path={path}/>)
)}
This gives me a syntax error.
I think this is because I am trying to render with a ForEach loop.
How do I fix this syntax problem?
You can use the Array#flatMap and Array#map function which will return an array of components which React can render.
{routes.flatMap(items => items.children).map(({ component, path }) => <Route component={component} exact path={path}/>)}
You were also missing some parens () around the curly braces which caused the Syntax error.
E.g. in the following part, you are trying to destructure the object containing the children property. However, you will need to wrap the destructing syntax with parens if you use this in an arrow functions.
{routes.map({children} =>
I have a left nav bar that utilises my admin.js file. This in turn imports my routes.js file which returns a const with an array. Is it possible to translate these items in any way using react-i18next without breaking the "hooks" rules?
Please note I've implemented react-i18next on my content pages and they work well. Not included any react-i18next imports on below code.
My Admin.js file
import React from "react";
import cx from "classnames";
import { Switch, Route, Redirect } from "react-router-dom";
// creates a beautiful scrollbar
import PerfectScrollbar from "perfect-scrollbar";
import "perfect-scrollbar/css/perfect-scrollbar.css";
// #material-ui/core components
import { makeStyles } from "#material-ui/core/styles";
// core components
import AdminNavbar from "components/Navbars/AdminNavbar.js";
import Footer from "components/Footer/Footer.js";
import Sidebar from "components/Sidebar/Sidebar.js";
import FixedPlugin from "components/FixedPlugin/FixedPlugin.js";
import routes from "routes.js";
import styles from "assets/jss/material-dashboard-pro-react/layouts/adminStyle.js";
var ps;
const useStyles = makeStyles(styles);
export default function Dashboard(props) {
const { ...rest } = props;
// states and functions
const [mobileOpen, setMobileOpen] = React.useState(false);
const [miniActive, setMiniActive] = React.useState(false);
const [image, setImage] = React.useState(require("assets/img/sidebar-2.jpg"));
const [color, setColor] = React.useState("blue");
const [bgColor, setBgColor] = React.useState("black");
// const [hasImage, setHasImage] = React.useState(true);
const [fixedClasses, setFixedClasses] = React.useState("dropdown");
const [logo, setLogo] = React.useState(require("assets/img/logo-white.svg"));
// styles
const classes = useStyles();
const mainPanelClasses =
classes.mainPanel +
" " +
cx({
[classes.mainPanelSidebarMini]: miniActive,
[classes.mainPanelWithPerfectScrollbar]:
navigator.platform.indexOf("Win") > -1
});
// ref for main panel div
const mainPanel = React.createRef();
// effect instead of componentDidMount, componentDidUpdate and componentWillUnmount
React.useEffect(() => {
if (navigator.platform.indexOf("Win") > -1) {
ps = new PerfectScrollbar(mainPanel.current, {
suppressScrollX: true,
suppressScrollY: false
});
document.body.style.overflow = "hidden";
}
window.addEventListener("resize", resizeFunction);
// Specify how to clean up after this effect:
return function cleanup() {
if (navigator.platform.indexOf("Win") > -1) {
ps.destroy();
}
window.removeEventListener("resize", resizeFunction);
};
});
// functions for changeing the states from components
const handleImageClick = image => {
setImage(image);
};
const handleColorClick = color => {
setColor(color);
};
const handleBgColorClick = bgColor => {
switch (bgColor) {
case "white":
setLogo(require("assets/img/logo.svg"));
break;
default:
setLogo(require("assets/img/logo-white.svg"));
break;
}
setBgColor(bgColor);
};
const handleFixedClick = () => {
if (fixedClasses === "dropdown") {
setFixedClasses("dropdown show");
} else {
setFixedClasses("dropdown");
}
};
const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};
const getRoute = () => {
return window.location.pathname !== "/admin/full-screen-maps";
};
const getActiveRoute = routes => {
let activeRoute = "Default Brand Text";
for (let i = 0; i < routes.length; i++) {
if (routes[i].collapse) {
let collapseActiveRoute = getActiveRoute(routes[i].views);
if (collapseActiveRoute !== activeRoute) {
return collapseActiveRoute;
}
} else {
if (
window.location.href.indexOf(routes[i].layout + routes[i].path) !== -1
) {
return routes[i].name;
}
}
}
return activeRoute;
};
const getRoutes = routes => {
return routes.map((prop, key) => {
if (prop.collapse) {
return getRoutes(prop.views);
}
if (prop.layout === "/admin") {
return (
<Route
path={prop.layout + prop.path}
component={prop.component}
key={key}
/>
);
} else {
return null;
}
});
};
const sidebarMinimize = () => {
setMiniActive(!miniActive);
};
const resizeFunction = () => {
if (window.innerWidth >= 960) {
setMobileOpen(false);
}
};
return (
<div className={classes.wrapper}>
<Sidebar
routes={routes}
logoText={"My App"}
logo={logo}
image={image}
handleDrawerToggle={handleDrawerToggle}
open={mobileOpen}
color={color}
bgColor={bgColor}
miniActive={miniActive}
{...rest}
/>
<div className={mainPanelClasses} ref={mainPanel}>
<AdminNavbar
sidebarMinimize={sidebarMinimize.bind(this)}
miniActive={miniActive}
brandText={getActiveRoute(routes)}
handleDrawerToggle={handleDrawerToggle}
{...rest}
/>
{/* On the /maps/full-screen-maps route we want the map to be on full screen - this is not possible if the content and conatiner classes are present because they have some paddings which would make the map smaller */}
{getRoute() ? (
<div className={classes.content}>
<div className={classes.container}>
<Switch>
{getRoutes(routes)}
<Redirect from="/admin" to="/admin/dashboard" />
</Switch>
</div>
</div>
) : (
<div className={classes.map}>
<Switch>
{getRoutes(routes)}
<Redirect from="/admin" to="/admin/dashboard" />
</Switch>
</div>
)}
{getRoute() ? <Footer fluid /> : null}
<FixedPlugin
handleImageClick={handleImageClick}
handleColorClick={handleColorClick}
handleBgColorClick={handleBgColorClick}
color={color}
bgColor={bgColor}
bgImage={image}
handleFixedClick={handleFixedClick}
fixedClasses={fixedClasses}
sidebarMinimize={sidebarMinimize.bind(this)}
miniActive={miniActive}
/>
</div>
</div>
);
}
My routes.js file (wanting to translate the "name" item)
const dashRoutes = [
{
path: "/dashboard",
name: "Dashboard", <--- need to translate this t("Dashboard") and subsequent name attributes below
rtlName: "لوحة القيادة",
icon: DashboardIcon,
component: Dashboard,
layout: "/admin"
},
{
collapse: true,
name: "Pages",
rtlName: "صفحات",
icon: Image,
state: "pageCollapse",
views: [
{
path: "/pricing-page",
name: "Pricing Page",
rtlName: "عالتسعير",
mini: "PP",
rtlMini: "ع",
component: PricingPage,
layout: "/auth"
},
{
path: "/rtl-support-page",
name: "RTL Support",
rtlName: "صودعم رتل",
mini: "RS",
rtlMini: "صو",
component: RTLSupport,
layout: "/rtl"
},
{
path: "/timeline-page",
name: "Timeline Page",
rtlName: "تيالجدول الزمني",
mini: "T",
rtlMini: "تي",
component: TimelinePage,
layout: "/admin"
},
{
path: "/login-page",
name: "Login Page",
rtlName: "هعذاتسجيل الدخول",
mini: "L",
rtlMini: "هعذا",
component: LoginPage,
layout: "/auth"
},
{
path: "/register-page",
name: "Register Page",
rtlName: "تسجيل",
mini: "R",
rtlMini: "صع",
component: RegisterPage,
layout: "/auth"
},
{
path: "/lock-screen-page",
name: "Lock Screen Page",
rtlName: "اقفل الشاشة",
mini: "LS",
rtlMini: "هذاع",
component: LockScreenPage,
layout: "/auth"
},
{
path: "/user-page",
name: "User Profile",
rtlName: "ملف تعريفي للمستخدم",
mini: "UP",
rtlMini: "شع",
component: UserProfile,
layout: "/admin"
},
{
path: "/error-page",
name: "Error Page",
rtlName: "صفحة الخطأ",
mini: "E",
rtlMini: "البريد",
component: ErrorPage,
layout: "/auth"
}
]
},
{
collapse: true,
name: "Components",
rtlName: "المكونات",
icon: Apps,
state: "componentsCollapse",
views: [
{
collapse: true,
name: "Multi Level Collapse",
rtlName: "انهيار متعدد المستويات",
mini: "MC",
rtlMini: "ر",
state: "multiCollapse",
views: [
{
path: "/buttons",
name: "Buttons",
rtlName: "وصفت",
mini: "B",
rtlMini: "ب",
component: Buttons,
layout: "/admin"
}
]
},
{
path: "/buttons",
name: "Buttons",
rtlName: "وصفت",
mini: "B",
rtlMini: "ب",
component: Buttons,
layout: "/admin"
},
{
path: "/grid-system",
name: "Grid System",
rtlName: "نظام الشبكة",
mini: "GS",
rtlMini: "زو",
component: GridSystem,
layout: "/admin"
},
{
path: "/panels",
name: "Panels",
rtlName: "لوحات",
mini: "P",
rtlMini: "ع",
component: Panels,
layout: "/admin"
},
{
path: "/sweet-alert",
name: "Sweet Alert",
rtlName: "الحلو تنبيه",
mini: "SA",
rtlMini: "ومن",
component: SweetAlert,
layout: "/admin"
},
{
path: "/notifications",
name: "Notifications",
rtlName: "إخطارات",
mini: "N",
rtlMini: "ن",
component: Notifications,
layout: "/admin"
},
{
path: "/icons",
name: "Icons",
rtlName: "الرموز",
mini: "I",
rtlMini: "و",
component: Icons,
layout: "/admin"
},
{
path: "/typography",
name: "Typography",
rtlName: "طباعة",
mini: "T",
rtlMini: "ر",
component: Typography,
layout: "/admin"
}
]
},
{
collapse: true,
name: "Forms",
rtlName: "إستمارات",
icon: "content_paste",
state: "formsCollapse",
views: [
{
path: "/regular-forms",
name: "Regular Forms",
rtlName: "أشكال عادية",
mini: "RF",
rtlMini: "صو",
component: RegularForms,
layout: "/admin"
},
{
path: "/extended-forms",
name: "Extended Forms",
rtlName: "نماذج موسعة",
mini: "EF",
rtlMini: "هوو",
component: ExtendedForms,
layout: "/admin"
},
{
path: "/validation-forms",
name: "Validation Forms",
rtlName: "نماذج التحقق من الصحة",
mini: "VF",
rtlMini: "تو",
component: ValidationForms,
layout: "/admin"
},
{
path: "/wizard",
name: "Wizard",
rtlName: "ساحر",
mini: "W",
rtlMini: "ث",
component: Wizard,
layout: "/admin"
}
]
},
{
collapse: true,
name: "Tables",
rtlName: "الجداول",
icon: GridOn,
state: "tablesCollapse",
views: [
{
path: "/regular-tables",
name: "Regular Tables",
rtlName: "طاولات عادية",
mini: "RT",
rtlMini: "صر",
component: RegularTables,
layout: "/admin"
},
{
path: "/extended-tables",
name: "Extended Tables",
rtlName: "جداول ممتدة",
mini: "ET",
rtlMini: "هور",
component: ExtendedTables,
layout: "/admin"
},
{
path: "/react-tables",
name: "React Tables",
rtlName: "رد فعل الطاولة",
mini: "RT",
rtlMini: "در",
component: ReactTables,
layout: "/admin"
}
]
},
{
collapse: true,
name: "Maps",
rtlName: "خرائط",
icon: Place,
state: "mapsCollapse",
views: [
{
path: "/google-maps",
name: "Google Maps",
rtlName: "خرائط جوجل",
mini: "GM",
rtlMini: "زم",
component: GoogleMaps,
layout: "/admin"
},
{
path: "/full-screen-maps",
name: "Full Screen Map",
rtlName: "خريطة كاملة الشاشة",
mini: "FSM",
rtlMini: "ووم",
component: FullScreenMap,
layout: "/admin"
},
{
path: "/vector-maps",
name: "Vector Map",
rtlName: "خريطة المتجه",
mini: "VM",
rtlMini: "تم",
component: VectorMap,
layout: "/admin"
}
]
},
{
path: "/widgets",
name: "Widgets",
rtlName: "الحاجيات",
icon: WidgetsIcon,
component: Widgets,
layout: "/admin"
},
{
path: "/charts",
name: "Charts",
rtlName: "الرسوم البيانية",
icon: Timeline,
component: Charts,
layout: "/admin"
},
{
path: "/calendar",
name: "Calendar",
rtlName: "التقويم",
icon: DateRange,
component: Calendar,
layout: "/admin"
}
];
export default dashRoutes;
you can do something like this.
import { withTranslation } from "react-i18next";
import i18n from "../../../../i18n" // import here your i18n file
const dashRoutes = [
{
path: "/dashboard",
name: i18n("Dashboard"), <--- need to translate this t("Dashboard") and subsequent name attributes below
rtlName: "لوحة القيادة",
icon: DashboardIcon,
component: Dashboard,
layout: "/admin"
},
{
collapse: true,
name: i18n("Page"),
rtlName: "صفحات",
icon: Image,
state: "pageCollapse",
......
export default withTranslation()(dashRoutes);
or you can use useTranslation
import { useTranslation } from "react-i18next";
const {t} = useTranslation();
const dashRoutes = [
{
path: "/dashboard",
name: t("Dashboard"), <--- need to translate this t("Dashboard") and subsequent name attributes below
rtlName: "لوحة القيادة",
icon: DashboardIcon,
component: Dashboard,
layout: "/admin"
},
...
i hope it can help you to translate your array name .