Frontend Navigation with Custom Actons
This doc covers how to handle client-side navigation in the application using nexus.
The application uses Reitit for routing in combination with our custom action/effects system for state management. This provides a powerful and flexible way to handle navigation in your Single Page Application (SPA).
Navigation Actions
There are main navigation actions available:
:router/navigate- An action that triggers navigation
Basic Navigation
Using :router/navigate
The most common way to trigger navigation is with the :router/navigate action:
{:on {:click [[:router/navigate {:to ::routes/dashboard}]]}}
This will navigate the user to the dashboard route defined in your routes configuration.
Navigation with Parameters
You can include path parameters and query parameters in your navigation:
{:on {:click [[:router/navigate
{:to ::routes/user-profile
:path-params {:user-id "123"}
:query-params {:tab "settings"}}]]}}
This will navigate to a URL like /user/123?tab=settings (assuming your route is defined as something like /user/:user-id).
Handling Navigation Results
When a navigation occurs, the route information is stored in DataScript and can be queried:
;; Query current route
(ds/q '[:find (pull ?e [*])
:where [?e :route/id :current-route]]
@!conn)
;; Or more frequently by entity
(ds/entity db [:route/id :current-route])
Common Navigation Patterns
Navigation after Success
A common pattern is to navigate after an API call succeeds:
{:on {:submit [[:data/command {:command/kind :some/endpoint
:command/data form-data}
{:on-success [[:router/navigate {:to ::routes/success-page}]]}]]}}
Conditional Navigation in Components
(defn login-form [ctx]
(let [auth-result (get-in ctx [:state :auth :result])]
[:form {:on {:submit [[:auth/login form-data]]}}
;; Form elements
(when (:success auth-result)
{:on {:mount [[:router/navigate {:route ::routes/dashboard}]]}})]))
Redirecting to Login
When an unauthorized action is attempted, redirect to the login page:
{:on {:error [[:router/navigate {:to ::routes/login}]]}}
There is also the [:saas.auth/maybe-init] action that checks if the user is logged in, fetches the auth information from the server and redirects to login if the user wasn't logged in.
Route Definition
Routes are defined in src/cljc/saas/routes.cljc using Reitit's routing syntax. Here's a simplified example:
(def routes
[""
["/" {:name ::index
:seo/title "Home"}]
["/dashboard" {:name ::dashboard
:seo/title "Dashboard"}]
["/user/:user-id" {:name ::user-profile
:seo/title "User Profile"
:view user/profile-page
:parameters {:path {:user-id string?}
:query {:tab string?}}}]])
SEO Considerations
When navigating to a new route, the navigation action will automatically update the page title and meta description based on the :seo/title and :seo/description properties defined in your route data.
Route Redirects
The application supports automatic redirects through the :redirect-to route property. This allows you to define routes that automatically redirect to another route when accessed.
How It Works
- Define a route with the
:redirect-toproperty insrc/cljc/saas/routes.cljc:
["/settings"
["" {:name ::settings
:router/redirect {:to ::profile})]
["/profile" ...]]
- When a user navigates to
/settings, the router detects the:router/redirectproperty and automatically redirects to the specified route (::routes/profile-settingsin this example).
Common Use Cases
- Default pages for sections (e.g., redirecting
/settingsto/settings/profile) - Legacy URLs that should redirect to new locations
- Creating logical groupings of routes with a default landing page
Implementation Details
The navigation system is implemented in:
src/cljs/saas/core.cljs: Sets up the router and initializes route handlingsrc/cljc/saas/actions.cljc: Defines the navigation action handlerssrc/cljc/saas/routes.cljc: Defines the application routessrc/cljs/saas/router.cljs: Handles route matching and redirection logic
Routing by route-name
You can use saas.routes/href to obtain the physical href location. Example: (href :saas.routes/login) => "/auth/login". This is handy if you prefer to use keyword based routing troughout the app as it is easier to do refactorings like this.
Example Usage
Basic Navigation Button
(defn nav-button []
[:button {:on {:click [[:router/navigate {:to ::routes/dashboard}]]}}
"Go to Dashboard"])
Navigation After Form Submission
(defn signup-form [ctx]
(let [form-data (get-form-data ctx :signup)]
[:form {:on {:submit [[:auth/register form-data]
[:router/navigate {:route ::routes/onboarding}]]}}
;; Form elements here
]))
Programmatic Navigation
You can also trigger navigation programmatically by dispatching actions:
;; In an action handler
(defn ^:action-handler handle-auth-success
[ctx action]
(match action
[:auth/success user-data]
{:transactions [{:user/id (:id user-data) :user/name (:name user-data)}]
:effects [[:router/navigate {:route ::routes/dashboard}]]}
:else nil))
This provides a clean, declarative way to handle navigation that integrates seamlessly with the rest of the application's action system.