-
Notifications
You must be signed in to change notification settings - Fork 4
Navigation
- Introduction
- Native navigation
- Web navigation
- Local navigation
- Remote navigation
- Push, pop and reset
- The Navigator
- The ControllerId
- Keep reading
Navigations are Beagle Actions, but we feel it needs its own dedicated topic.
A navigation in Beagle can be of 4 types:
- Native: changes the current named route in the frontend app.
- Web: opens the browser with the given URL.
- Local: navigates to a Beagle screen that has been declared inline in the JSON.
- Remote: navigates to the Beagle screen pointed by the given URL.
To make a native navigation, use the action
openNativeRoute
and pass the name of the route in the frontend you want to navigate to. Example:
import { BeagleJSX } from '@zup-it/beagle-backend-core'
import { openNativeRoute } from '@zup-it/beagle-backend-core/actions'
import { Button } from '@zup-it/beagle-backend-components'
import { Screen } from '@zup-it/beagle-backend-express'
export const MyScreen: Screen = () => (
<Button onPress={openNativeRoute({ route: 'product', data: { id: '1' } })}>
Check the details of the product with id 1
</Button>
)
To open a native route, we need to pass the route name in the frontend and, optionally, some data to pass to this route. In the example above we open the native route named "product" and we pass "id: 1" as a parameter.
To open a web page in the system's browser, use the action
openExternalUrl
and pass the page's URL. Example:
import { BeagleJSX } from '@zup-it/beagle-backend-core'
import { openExternalUrl } from '@zup-it/beagle-backend-core/actions'
import { Button } from '@zup-it/beagle-backend-components'
import { Screen } from '@zup-it/beagle-backend-express'
export const MyScreen: Screen = () => (
<Button onPress={openExternalUrl('https://www.google.com')}>
Open Google
</Button>
)
openExternalUrl
has 2 signatures (overload): you can either pass the URL as the first argument or an object
containing both the URL and analytics options.
Local and Remote navigations are very similar, they all use the same actions. The only difference is that local navigations specify the new screen in the body of the action (inline), while remote navigations only specifies a URL to the new screen.
Example of a local navigation:
import { BeagleJSX } from '@zup-it/beagle-backend-core'
import { pushView, popView } from '@zup-it/beagle-backend-core/actions'
import { Button } from '@zup-it/beagle-backend-components'
import { Screen } from '@zup-it/beagle-backend-express'
const navigateToLocalRoute = pushView({
route: {
screen: (
<Container id="my-local-route">
<>Hello from the local route!</>
<Button onPress={popView()}>Go back</Button>
</Container>
)
}
})
export const MyScreen: Screen = () => <Button onPress={navigateToLocalRoute}>Open Local Route</Button>
Above, we pass a component tree to the property screen
. This is the screen that will be rendered when the user
performs the navigation. Since we need to name every screen in the frontend and there's no URL for this, an id must be
given to the root component, it will be used as the screen identifier.
Although we only used route
in this example, a local navigation can also have analytics
(Analytics) and
navigationContext
(Context - Navigation Contexts).
A local screen can be created with any of these actions: pushStack
, pushView
, resetStack
, resetApplication
.
Remote navigations work just like local navigations, but instead of providing an object with a screen
(LocalView
)
to the property route
, we need to provide an object of type
RemoteView
.
A RemoteView
must contain a URL to the screen we want to navigate to, the other parameters are optional:
-
url
: the URL of the screen in the backend. -
shouldPrefetch
: optional. When true, it fetches the screen as soon as possible, so there's no wait time when the navigation actually happens. -
fallback
: optional. A component tree to show in case the navigation fails to fetch the screen. -
httpAdditionalData
: optional. Further configuration for the request: method (get
by default), headers and body. -
navigationContext
: optional. A Navigation Context for this navigation.
See the example below of how to make a remote navigation:
import { BeagleJSX } from '@zup-it/beagle-backend-core'
import { pushView, popView } from '@zup-it/beagle-backend-core/actions'
import { Button } from '@zup-it/beagle-backend-components'
import { Screen } from '@zup-it/beagle-backend-express'
const navigateToShoppingCart = pushView({
route: {
url: '/shopping-cart',
fallback: <>Oops! Could load the screen you requested</>
}
})
export const MyScreen: Screen = () => <Button onPress={navigateToShoppingCart}>Go to shopping cart</Button>
Above, we created a button that, when pressed, fetches the screen at $backendUrl/shopping-cart
and navigates to it. If
the fetch fails, it replaces the current screen with an error message.
The Beagle navigation structure is a stack of stacks of screens. Imagine an app with a bottom navigator and the sections
"Home", "Products", "Shopping Cart" and "Account". The bottom navigator would be the parent stack. Each of these menus
would be a stack in the navigator containing the pages themselves. If you don't need this structure, you can just always
use the first stack by only calling the navigation actions pushView
, popView
and popToView
, ignoring the rest.
Both local and remote navigations can be pushes, pops or resets. All previous examples used the action pushView
and
popView
, but we could have used any of the navigation actions for beagle screens:
-
pushView
: adds the screen to the current navigation stack. If no navigation stack exists yet, it creates one. -
pushStack
: adds the screen to a new navigation stack. -
popView
: removes the screen at the top of the current stack, effectively going back to the previous screen. -
popToView
: removes every screen until the screen identifier (url or id) provided is found, effectively going back to the screen identified by the string passed as parameter. If the screen is not found, nothing happens. -
popStack
: removes the current stack, effectively going back to the last screen of the last stack. -
resetStack
: replaces the current stack with a new stack that starts with the screen passed as parameter. -
resetApplication
: replaces all stacks with a new stack that starts with the screen passed as parameter.
Important: if you're using the module
@zup-it/beagle-backend-express
, for remote navigations, prefer using theNavigator
instead of these actions directly.
When using the Beagle Backend TS with Express (@zup-it/beagle-backend-express
) you shouldn't use the navigation
actions directly to make remote navigations. Instead, you should use the Navigator
, which is type-safe way of
making navigations.
Each screen receives the Navigator as one of its properties and the Navigator has every method listed in the previous section. The only difference is that, instead of dealing with strings, it uses the screens themselves to make a navigation. This ensures we never use an incorrect URL or try to navigate to a screen that doesn't exist. Furthermore, the Navigator ensures that everything expected by a screen is correctly passed when we try to navigate to it.
The example below shows two screens: the first is a page that displays the details of a product. The product is expected to be passed in the navigation context. The second is a screen with 3 buttons representing each one a product. When the button is pressed, it navigates to the screen that shows its details.
product.tsx
import { BeagleJSX } from '@zup-it/beagle-backend-core'
import { Screen, ScreenRequest } from '@zup-it/beagle-backend-express'
import { Product } from '../model/product' // no need to show this interface to explain this topic
interface ProductScreenRequest extends ScreenRequest {
navigationContext: {
product: Product,
}
}
export const ProductScreen: Screen<ProductScreenRequest> = ({ navigationContext }) => (
<>
<>{navigationContext.get('product').get('name')}</>
<>{navigationContext.get('product').get('description')}</>
<>{navigationContext.get('product').get('price')}</>
</>
)
product-list.tsx
import { BeagleJSX } from '@zup-it/beagle-backend-core'
import { Button } from '@zup-it/beagle-backend-components'
import { Screen } from '@zup-it/beagle-backend-express'
import { Product } from '../model/product' // no need to show this interface to explain this topic
import { ProductScreen } from './product'
const products: Product[] = [
// ... (suppose we declared 3 products here)
]
export const ProductListScreen: Screen = ({ navigator }) => (
<>
<Button onPress={navigator.pushView(ProductScreen, { navigationContext: products[0] })}>Product 1</Button>
<Button onPress={navigator.pushView(ProductScreen, { navigationContext: products[1] })}>Product 2</Button>
<Button onPress={navigator.pushView(ProductScreen, { navigationContext: products[2] })}>Product 3</Button>
</>
)
Notice that we imported the screen from the file we declared it and when navigating to it, we specified the product
it expects in the navigation context. If we hadn't passed the product, this code wouldn't compile, because when we
created the ProductScreen
, we said it needed a navigation context with a product.
Some aspects of the navigation like how to show the loading feedback, the animations and the error treatment can be customized with Navigation Controllers in the frontend (check the documentation for your frontend lib to know more details about this).
Each navigation stack is associated to a Navigation Controller, which can be identified by an id. When changing the
current stack (pushStack
, resetStack
and resetApplication
), the Navigation Controller of the new stack can be
customized by passing the property controllerId
.
Next topic: Making requests