Getting Started with Fable. Routing


Photo by Diego Jimenez on Unsplash

Welcome back! This article is the part of “Getting started with Fable” series, so if you missed the previous articles please visit:

The idea of these series is to create a template which can be used as a starting point for enterprise application of any complexity. So as I’d love to see a functional application with routing, state management, unit tests, end-to-end tests, all possible linting tools, code style checkers, environment dependant configuration, build scripts (I hope I didn’t forget anything important).

The main topic of today’s post is application’s routing. I wanted to integrate React Router to the Fable-based application and as far as you are reading this post I’ve succeeded. And yes, I know that there is a Elmish Router which can be used in Fable applications. But there are couple of reasons why I’m not using it:

  • It’s an out-of-the-box solution. So it’s just working. It’s too simple, no challenge;
  • Most of the front-end engineers are not aware about Elm architecture for front-end applications. The most important part of my “investigation” is whether it’s possible to build application which is readable/maintainable by both .NET and front-end developers and at the same time taking advantage of functional programming as a main paradigm;
  • Integrating a third-party React component is in general very important exercise. Nowadays, it’s mostly impossible to develop big enterprise applications without consuming a components library or several independent components (like grid, drag-and-drop lists, etc.).

So yes, I’m aware about Elm architecture but for this series I’ll avoid it.

CI

There are couple of changes in the solution since the last post. And the first thing is integration with GitHub actions. Now Yarn is available as a part of the default image, hence you must remove a couple of lines from the configuration file as in the screenshot below. Otherwise you’ll experience build failures with messages saying that “yarn is already installed”:

Changes in the GitHub Actions configuration

Just a small quality of life change from the GitHub Actions side. I’m not sure about the current state of NPM but a year ago when I was responsible for configuring the CI environment for my Angular project I faced couple a of issues with NPM which where resolved by migrating to Yarn. That’s why on all new projects I’m starting I prefer using Yarn instead of NPM.

One last word regarding Yarn. To keep all your dependencies up to date you can use an interactive console, which is very handy. Just type:

yarn upgrade-interactive --latest

and Yarn will provide you a way to easily select the packages you want to upgrade (--latest parameter can be omitted in case you want to upgrade packages using version patterns provided in packages.json):

yarn upgrade-interactive interface

DevServer

Before start with the main dish let’s make one more improvement to our solution. It’s really hard to imagine a web development project without a development server. So let’s get one set up:

yarn add webpack-dev-server --dev

After installing the dependency you need to add a new script to the package.json file like on the screenshot below (in this and further articles I’ll post some minor code changes as screenshots to emphasize change itself):

New script

The next step is to configure this package:

// imports
module.exports = {
entry: './src/app.fsproj',
devServer: {
contentBase: './dist',
open: 'google-chrome',
historyApiFallback: true
},
// rest of the config
};
webpack.config.js

There are three properties we want to configure right now. The first one is contentBase. It’s responsible for configuring the output directory which will be used as a root for the web server. The second one, open, is not required and can be omitted. This is just a small automation script, which will open our application in the specified browser (in my case – Google Chrome). Please, take into account that it’s not recommended to commit this property, because it’s OS dependant (technical limitation), so if you have a team members which are using different OS (MacOS, Linux, Windows) it’s very likely that some of them will have issues with it. And the last but not least is historyApiFallback. This one is mandatory for client-side routing in Single Page Applications. So in case when you have only one index.html page and all other routes make sense only on the client (/users, /admin/sessions, etc.) you want the server to always return that single page. That’s where this property comes into play.

Now all you need is to run command:

yarn start

and application will be opened in the browser of choice (by default application will be served at http://localhost:8080/).

Routing

Here we come. Now when we are finished with all the preparations we can start developing the application itself (finally). Most of the modern applications are Single Page Application, it mean that we need a routing to display different pages on the client side without actual navigation.

At first, we need to install the React Router (it’s the most popular routing module for the React applications, I see no reason to use something else):

yarn add react-router-dom --dev

And now it’s the place where thing getting more interesting. I need to warn you that I’m not an expert in F# (not even close), I’m just a person who is trying to learn by doing (making mistakes is included). So, what I did:

  • There is a manual in the Fable React repository (link). I used it as a starting point.
  • For the initial showcase I needed a couple of components from the React Router: BrowserRouter, Link, Switch, Route. I’ve created a file for each of the classes. As for me, the process looks similar to writing a type definition for libraries in TypeScript (that’s not a surprise, because actually it was a sort of type definition but for F#.
  • Then I’ve declared discriminated unions for each set of attributes (parameters) and functions for each component.
module ReactRouter.BrowserRouter
open Fable.Core
open Fable.Core.JsInterop
open Fable.React
type BrowserRouterProps =
| BaseName of string
| GetUserConfirmation of (string -> (bool -> unit) -> unit)
| ForceRefresh of bool
| KeyLength of int
let inline BrowserRouter (props: BrowserRouterProps list) (elems: ReactElement list): ReactElement =
ofImport "BrowserRouter" "react-router-dom" (keyValueList CaseRules.LowerFirst props) elems
browser-router.fs
view raw link.fs hosted with ❤ by GitHub
link.fs
module ReactRouter.Route
open Fable.Core
open Fable.Core.JsInterop
open Fable.React
type RouteProps =
| Path of string
let inline Route (props: RouteProps list) (elems: ReactElement list): ReactElement =
ofImport "Route" "react-router-dom" (keyValueList CaseRules.LowerFirst props) elems
view raw route.fs hosted with ❤ by GitHub
route.fs
module ReactRouter.Switch
open Fable.Core
open Fable.Core.JsInterop
open Fable.React
let inline Switch (props: unit list) (elems: ReactElement list): ReactElement =
ofImport "Switch" "react-router-dom" (keyValueList CaseRules.LowerFirst props) elems
view raw switch.fs hosted with ❤ by GitHub
switch.fs

Most of the code doesn’t look like rocket science, but there is an interesting code related to JavaScript interop. It’s not a secret that in JavaScript functions often receive one parameter which can be of different types (e.g. navigation URL can be simple string or an object with base URL and query parameters as fields). In the F# there is no way to define such type that’s why Fable provides a generic type U* (U2, U3, etc.) to make it work. You can see the usage of this type in the link.fs file.

And now the only thing left is to actually use these definitions. For this purpose let’s modify the App component to create two stub pages with different welcome messages:

module App
open Fable.React
open Fable.Core.JsInterop
open ReactRouter.BrowserRouter
open ReactRouter.Link
open ReactRouter.Switch
open ReactRouter.Route
let App() =
BrowserRouter [] [
div [] [str "Hello from Fable-React Application!"]
Link [To (!^ "/about")] [str "About"]
Switch [] [
Route [Path "/about"] [str "Content of the About page!"]
Route [Path "/"] [str "Content of the Home page!"]
]
]
view raw app.fs hosted with ❤ by GitHub
app.fs

The thing I like about this code is that it looks so much similar to the JSX. Seriously, I have an impression that most of the JavaScript/React developers can read this code without significant issues. Each function in this syntax accepts two parameters – two arrays. The first array describes the element attributes, the second – its children.

The only thing I’d like to mention is the !^ operator – it’s the Fable’s type casting operator, it’s required to work with U* generic type. This operator is a sort of syntax sugar for the alternative version which is not so convenient to use: U3.Case*. Case1 means first type from the generic type declaration.

Now you can start the application and see that it’s working!

Running application

conclusion

That’s basically it for today. In the next posts I’ll cover even more development topics, so please stay tuned. If you have any questions or faced any issues during following this manual don’t hesitate to write in the comments below.


Thanks a lot for reading! I hope you enjoyed. If you find this material useful, don’t forget to subscribe and share with your colleagues and friends! Thanks!

7 thoughts on “Getting Started with Fable. Routing

  1. These posts are awesome. Thank you for taking the time to do them. A lot of Fable/Elm/React tutorials jump straight without going so in depth on each individual step, and your guides are exactly what I have been wanting.

    Looking forward to more!

    Liked by 1 person

Leave a reply to Istvan (@lix) Cancel reply