Frontend Navigation with Re-frame
This document covers how to handle client-side navigation in the application using Re-frame events.
Overview
The application uses Reitit for routing in combination with Re-frame for state management. This provides a powerful and flexible way to handle navigation in your Single Page Application (SPA).
Navigation Events
There are two main navigation events available:
:router/navigate
- A regular event that triggers navigation:router/navigate!
- An effect that performs immediate navigation:router/redirect
- An event triggered when a route has a:redirect-to
property
Basic Navigation
Using :router/navigate
:router/navigate
The most common way to trigger navigation is with the :router/navigate
event:
(rf/dispatch [:router/navigate {:route ::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:
(rf/dispatch [:router/navigate
{:route ::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
).
Direct Navigation Effect
In some cases, you might want to use the navigation effect directly in a custom event handler:
(rf/reg-event-fx
::my-custom-event
(fn [{:keys [db]} [_ some-data]]
;; Do something with the data
{:db (assoc db :some-key some-data)
:router/navigate! {:route ::routes/success-page}}))
The :router/navigate!
effect performs the navigation immediately as a side effect.
Handling Navigation Results
When a navigation occurs, the :router/navigated
event is dispatched with the new route details. This event updates the application state with the current route information and sets any SEO-related metadata.
The route information is stored in the app db under :router/current-route
and can be accessed with a subscription:
(rf/subscribe [:router/current-route])
Common Navigation Patterns
Navigation after Success
A common pattern is to navigate after an API call succeeds:
(rf/dispatch
[:http/api
{:method :post
:url :some/endpoint
:body data
:on-success [:router/navigate {:route ::routes/success-page}]}])
Conditional Navigation in an Event Handler
(rf/reg-event-fx
::process-auth
(fn [{:keys [db]} [_ auth-result]]
(if (:success auth-result)
{:db (assoc db :auth auth-result)
:router/navigate! {:route ::routes/dashboard}}
{:db (assoc db :auth-error (:error auth-result))})))
Redirecting to Login
When an unauthorized action is attempted, it's common to redirect to the login page:
(rf/reg-event-fx
::handle-unauthorized
(fn [{:keys [db]} _]
{:db (dissoc db :auth)
:router/navigate! {:route ::routes/login}}))
Route Definition
Routes are defined in src/cljc/saas/common/routes.cljc
using Reitit's routing syntax. Here's a simplified example:
(defn app-routes []
[""
["/" {:name ::routes/index
:seo/title "Home"}]
["/dashboard" {:name ::routes/dashboard
:seo/title "Dashboard"
:view dashboard/dashboard-page}]
["/user/:user-id" {:name ::routes/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 :router/navigated
event 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-to
property insrc/cljc/saas/common/routes.cljc
:
["/settings"
["" (merge {:name :routes.settings/index
:redirect-to :routes.settings/profile}
#?(:cljs {:view #($ :div)}))]
["/profile" ...]]
When a user navigates to
/settings
, the router detects the:redirect-to
property and automatically redirects to the specified route (:routes.settings/profile
in this example).The redirection is handled by the
:router/redirect
event, which is dispatched when a route with:redirect-to
is matched.
Common Use Cases
Default pages for sections (e.g., redirecting
/settings
to/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/cljs/saas/db/events.cljs
: Defines the navigation events and effectssrc/cljc/saas/common/routes.cljc
: Defines the application routessrc/cljs/saas/routing.cljs
: Handles route matching and redirection logic
Example Usage
Basic Navigation Button
(defui nav-button []
($ ui/button
{:on-click #(rf/dispatch [:router/navigate {:route ::routes/dashboard}])}
"Go to Dashboard"))
Navigation After Form Submission
(defui signup-form []
(let [form-data (use-state {:email "" :password ""})
handle-submit (fn [e]
(.preventDefault e)
(rf/dispatch [:auth/register
@form-data
;; Navigate after successful registration
{:on-success [:router/navigate {:route ::routes/onboarding}]}]))]
($ :form {:on-submit handle-submit}
;; Form elements here
)))
Last updated