Core components
Client core
Client-side routing and navigation
25 min
overview the client side routing and navigation system enables users to move between different views or "pages" within the ir engine client without requiring full page reloads it creates a seamless, application like experience by dynamically swapping content based on the current url path by leveraging react router and a custom routerservice, the system provides both declarative route definitions and programmatic navigation capabilities this chapter explores the implementation, workflow, and integration of client side routing within the ir engine client core concepts single page applications traditional websites typically load a new html page from the server for each url the user visits in contrast, single page applications (spas) like the ir engine client load once the application loads a single html page initially dynamic content javascript dynamically updates the content as users navigate no page reloads navigation between "pages" happens without refreshing the browser state preservation application state can be maintained across views this approach provides several benefits faster navigation between views smoother transitions and animations persistent state across different sections reduced server load for page rendering client side routing client side routing is the mechanism that enables spas to match urls to views associate url paths with specific components to render handle navigation update the browser's address bar without page reloads manage history support browser back/forward buttons and bookmarking control access restrict navigation based on authentication or permissions the ir engine client implements these capabilities using react router and a custom routerservice implementation route definition routes are defined using react router's components // simplified example of route definitions import { routes, route } from 'react router dom'; import homepage from ' /pages/homepage'; import profilepage from ' /pages/profilepage'; import admindashboard from ' /admin/admindashboard'; function approutes() { return ( \<routes> \<route path="/" element={\<homepage />} /> \<route path="/profile" element={\<profilepage />} /> \<route path="/admin" element={\<admindashboard />} /> {/ additional routes /} \</routes> ); } this code imports the necessary components from react router defines a component that contains route definitions maps url paths to specific react components creates a routing structure for the application when a user navigates to a specific url, react router matches the path and renders the corresponding component navigation links users navigate between routes using the link component // example of navigation links import { link } from 'react router dom'; function navigationbar() { return ( \<nav> \<link to="/">home\</link> \<link to="/profile">my profile\</link> \<link to="/admin">admin area\</link> \</nav> ); } the link component renders as an html anchor ( \<a> ) element intercepts clicks to prevent default browser navigation updates the url using the history api triggers react router to render the matching component this creates a navigation experience that feels like traditional links but avoids full page reloads routerservice the ir engine client extends react router with a custom routerservice that provides programmatic navigation // simplified from src/common/services/routerservice tsx import { createbrowserhistory, history } from 'history'; import { definestate } from '@ir engine/hyperflux'; // create a history object to manage browser session history export const history history = createbrowserhistory(); // define a hyperflux state for router actions export const routerstate = definestate({ name 'routerstate', initial {}, // action to navigate programmatically navigate (pathname string, searchparams = {}) => { // convert searchparams object to url query string if provided let search = ''; if (object keys(searchparams) length > 0) { const params = new urlsearchparams(); for (const \[key, value] of object entries(searchparams)) { params append(key, string(value)); } search = `?${params tostring()}`; } // update browser history and trigger route change history push({ pathname, search }); } }); this service creates a custom history object using the history library defines a hyperflux state with a navigate action provides a convenient method to trigger navigation from anywhere in the application supports adding query parameters to the url redirect component for declarative redirects, the ir engine client includes a redirect component // simplified from src/common/components/redirect tsx import react, { useeffect } from 'react'; import { routerstate } from ' /services/routerservice'; export const redirect = (props { to string }) => { useeffect(() => { // navigate to the specified path when the component mounts routerstate navigate(props to); }, \[props to]); // this component doesn't render anything visible return null; }; this component takes a destination path as a prop uses the useeffect hook to trigger navigation when mounted provides a declarative way to redirect users in jsx integration with react router the routerservice integrates with react router through a custom router setup // simplified concept of how the router is configured import react from 'react'; import { router } from 'react router dom'; import { history } from ' /common/services/routerservice'; import approutes from ' /approutes'; function mainapplication() { return ( // use router with our custom history object \<router location={history location} navigator={history}> \<approutes /> \</router> ); } this setup uses react router's low level router component connects it to our custom history object ensures that both react router and routerservice use the same history instance enables consistent navigation behavior throughout the application navigation workflow the process of client side navigation follows this sequence sequencediagram participant user participant link as link component participant routerservice as routerservice participant history as browser history api participant router as react router participant component as target component user >>link clicks navigation link link >>routerservice intercepts click event routerservice >>history updates url (history push) history >>routerservice confirms url change routerservice >>router notifies of location change router >>router matches new url to route definition router >>component renders matching component component >>user displays new view this diagram illustrates the user clicks a navigation link the routerservice updates the browser url without a page reload react router detects the url change and finds the matching route the corresponding component is rendered, updating the view for programmatic navigation (using routerstate navigate() ), the process is similar but starts from code rather than a user click protected routes the routing system integrates with authentication to create protected routes // example of a protected route component import react, { useeffect } from 'react'; import { usemutablestate } from '@ir engine/hyperflux'; import { authstate } from ' /user/services/authstate'; import { routerstate } from ' /common/services/routerservice'; function protectedroute({ children }) { const authstate = usemutablestate(authstate); const isauthenticated = authstate isauthenticated value; useeffect(() => { if (!isauthenticated) { // redirect to login if not authenticated routerstate navigate('/login', { redirecturl window\ location pathname }); } }, \[isauthenticated]); // only render children if authenticated return isauthenticated ? children null; } // usage in route definitions function approutes() { return ( \<routes> \<route path="/" element={\<homepage />} /> \<route path="/profile" element={ \<protectedroute> \<profilepage /> \</protectedroute> } /> {/ other routes /} \</routes> ); } this implementation checks authentication status using hyperflux state redirects unauthenticated users to the login page includes the original destination as a query parameter for post login redirection only renders the protected content if the user is authenticated nested routes for complex sections like the admin panel, the ir engine client uses nested routes // simplified example of nested routes in the admin panel import react from 'react'; import { routes, route, link } from 'react router dom'; // admin panel components const usermanagement = () => \<div>user management\</div>; const sitesettings = () => \<div>site settings\</div>; const admindashboard = () => \<div>admin dashboard\</div>; function adminroutes() { return ( \<div classname="admin layout"> \<nav classname="admin sidebar"> \<link to="/admin">dashboard\</link> \<link to="/admin/users">users\</link> \<link to="/admin/settings">settings\</link> \</nav> \<div classname="admin content"> \<routes> \<route path="/" element={\<admindashboard />} /> \<route path="users" element={\<usermanagement />} /> \<route path="settings" element={\<sitesettings />} /> \</routes> \</div> \</div> ); } // in the main routes file function approutes() { return ( \<routes> \<route path="/" element={\<homepage />} /> \<route path="/admin/ " element={\<adminroutes />} /> {/ other routes /} \</routes> ); } this approach uses the / wildcard to match all paths under /admin defines a separate set of routes within the admin component maintains a consistent admin layout while changing only the content area allows for modular organization of complex sections integration with other components the routing system integrates with several other components of the ir engine client hyperflux state management the routerservice uses hyperflux for state management // example of routing state in hyperflux import { definestate, getmutablestate } from '@ir engine/hyperflux'; // define routing state export const routerstate = definestate({ name 'routerstate', initial { currentpath window\ location pathname, previouspath null }, // navigation action navigate (pathname) => { // update state before navigation const state = getmutablestate(routerstate); state previouspath set(state currentpath value); state currentpath set(pathname); // perform actual navigation history push(pathname); } }); this integration tracks the current and previous paths in hyperflux state allows components to react to route changes provides a centralized way to manage navigation state authentication and authorization routing works closely with authentication to control access // example of route access control in the admin panel import { useeffect } from 'react'; import { usefind } from '@ir engine/common'; import { scopepath } from '@ir engine/common/src/schema type module'; import { usemutablestate } from '@ir engine/hyperflux'; import { allowedadminroutesstate } from ' /allowedadminroutesstate'; import { routerstate } from ' /common/services/routerservice'; function adminaccesscheck() { const currentuserid = engine instance userid; // fetch user scopes from the server const scopequery = usefind(scopepath, { query { userid currentuserid } }); useeffect(() => { if (scopequery data) { // check if the user has admin permission const isadmin = scopequery data some(scope => scope type === 'admin\ admin' ); if (!isadmin) { // redirect unauthorized users routerstate navigate('/'); } } }, \[scopequery data]); // render loading or admin content return scopequery data ? ( scopequery data some(scope => scope type === 'admin\ admin') ? ( \<adminpanel /> ) ( \<div>unauthorized redirecting \</div> ) ) ( \<div>loading \</div> ); } this integration fetches user permissions using feathersjs services checks if the user has the required permissions redirects unauthorized users to an appropriate page controls access to protected sections of the application url parameters and query strings the routing system supports url parameters and query strings // example of route with parameters \<route path="/user/\ userid" element={\<userprofile />} /> // in the userprofile component import { useparams, usesearchparams } from 'react router dom'; function userprofile() { // get url parameters const { userid } = useparams(); // get query parameters const \[searchparams] = usesearchparams(); const tab = searchparams get('tab') || 'profile'; return ( \<div> \<h1>user profile {userid}\</h1> \<div classname="tabs"> \<button classname={tab === 'profile' ? 'active' ''}> profile \</button> \<button classname={tab === 'settings' ? 'active' ''}> settings \</button> \</div> {/ render content based on tab /} \</div> ); } this functionality extracts dynamic values from the url path retrieves optional parameters from the query string enables dynamic content based on url parameters supports bookmarkable application states benefits of client side routing the client side routing and navigation system provides several key advantages improved performance faster navigation without full page reloads enhanced user experience smooth transitions between views state preservation maintains application state across navigation deep linking supports bookmarkable urls for specific application states code organization structures the application into logical sections access control integrates with authentication to protect sensitive areas familiar navigation preserves expected browser behavior like back/forward buttons these benefits make client side routing an essential component of the ir engine client architecture next steps with an understanding of how users navigate between different views, the next chapter explores how the application creates user interfaces within the 3d environment next xrui and in world widgets docid\ h9syagtpiz4e1zvhbenv