Photo © Nicolas Raymond (aka somadjinn)

Elm Architecture & Side Effects: example with Snabbdom/JSX

Yassine Elouafi

--

In a previous post we saw how to write web applications based on the Elm architectural pattern (using plain JavaScript and Snabbdom for Virtual DOM rendering). The main benefit of Elm architecture being that we can describe the application logic as a set of pure functions.

This article treats the next logical step: how to deal with Side Effects in the pure Elm model : server API calls, Asynchronous Actions …

The Login example

During this post we’ll develop a simple login application : the user is provided with a login form when he must enter a username and a password. Then the entered credentials are validated against a remote API, if the login is successful the user is redirected either to an admin page if he has the admin role or a guest page otherwise.

We’ll start with a pure version without side effects, then we’ll enhance the application by introducing side effects and a nested hierarchy of components.

To stay focused the API will be mocked by a simple local timeout-ed function.

Full sources of this example are here

The Setup

To start our project we need the following libs:

  • snabbdom : for Virtual DOM creation and patching
  • snabbdom-jsx : to generate Snabbdom virtual nodes using JSX
  • union-type: to represent Actions

We’ll also need babel to compile ES2015 and JSX code into compatible JavaScript code. To build the browser bundle, you can either use Browserify or Webpack; in this post i’ll use the first one.

You can use the following into your package.json file to get things up quickly

then run ‘npm install’ to pull all dependencies

To host the JS application w’ll use this index.html file in the root of our example directory

You can get the ‘style.css’ file from here.

Finally create a ‘js’ sub-directory. All our application code will go inside it. The final directory layout should look like

Et voilà! We’re ready to start

The Login Component : iteration 1

Create a Login.js file inside the js directory (from now on all JavaScript files must be put here). Here is our first version

Our component defines 2 functions, ‘init’ which returns the initial state and ‘view’ which constructs a virtual node from a given state. For now, ‘Login’ is passive and doesn’t fire any action so we don’t have Actions or an ‘update’ function. All we do is showing data we get from the input state.

To bootstrap our application. Create a ‘main.js’ file then copy the following content

The main module connects the component to the real DOM via the ‘dispatch’ function and continually reacts to all state changes by constructing new virtual nodes then patching the real DOM with them.

All we have to do now is

npm run build

It should compile all JavaScript files in the js directory and generate a ‘build.js’ file in the root of the example directory. Now open the index.html in the browser and normally you should have something like this

If something goes wrong, let me a comment or create an issue in the GitHub repository.

The Login Component : iteration 2

Now let’s add some action. First we’d like to save the user input into our state. We’ll also add a status message below the button, when the user submits the form, we’ll show the entered credentials in the status message. Later we’ll use it to show error messages.

Below the modifications to our code

So here is what we did:

  1. We added 3 actions, 2 to get the ‘name’ and ‘password’ inputs, and one to react to the submit event of the form.
  2. Since our data model has changed, the ‘init’ function also initializes the status message with an empty string.
  3. We added 2 event handlers to hookup user events (see below)
  4. updated the view : we added the status message and connected DOM events to our event handlers
  5. Handled the actions in an ‘update’ function by setting the appropriate field (using the awesome (…) spread ES7 syntax thanks Babel!).

‘onInput’ is a Higher Order Function, it takes an action, a dispatcher and returns another function : this is the real event handler that takes the input event, extracts the input string from event.target and then calls dispatch with that result.

In the view each input field creates its appropriate event handler by providing its particular action to the Higher Order Function ‘onInput’. This way we don’t have to create an event handler for each input for our view, especially, if you have a bunch of input fields in your form. Just one parametrized function to rule them all.

Actually, we can do better than that. Since we’ll likely need to extract the user input from event.target quite a lot, let’s create a helper function for that. Create a ‘helpers.js’ file and add the following function

Some of you may have already guessed. For ‘pipe’ it’s just function composition but in the other sens : take an input and send it through a pipeline of functions. Much like node pipes

Now back to the Login.js

This is surely much better. Our event handlers are directly created by the pipe function. The event object travels through a set of pure functions until it reaches the dispatch callback.

‘onSubmit’ is another Higher Order Function (or a curried function if you like) which returns the form submit handler. It fires the Login action and do some necessary dirty stuff to prevent the browser from refreshing the web page.

That’s it, hit ‘npm run build’ again and refresh your browser page. Enter some data in the form and hit RETURN or click the Submit button. Normally you should see the entered data below the button.

Now let’s get to the dirty stuff : Side effects.

The Login Component : iteration 3

We’ll now make our Login a bit more realistic. Submitting the form should trigger some API call to validate the entered credentials, and get the response : for now we’ll just show a success or an error message. Later we’ll see how to redirect to an appropriate page on a successful login.

We’ll mock our API call with a simple local function using setTimeout, this way we can test our code locally without need to create a server side code.

Create an ‘api.js’ file with the following content

For our testing purpose, a simple array will be sufficient. The exported function delays the response by 200ms to simulate a server call.

Now to the real question.

When and where should we make the API call ?

Well, as it turns out, there can be multiples answers.

In this post and the followings, w’ll expose 3 possible solutions

  1. Make the API call directly in the event handler
  2. The standard Elm solution : changes the signature of the ‘update’ function to make it return the desired Side Effect (update : (state, action) → (state, Effect).
  3. The third is inspired by this interesting post on the Redux Github site. And tries to address an issue we’ll see with the Elm approach.

Actually, all the 3 solutions share a common pattern, a side effect (like an asynchronous call) commonly involves 3 actions.

  1. A start action, before we trigger the side effect. In response to this action, the update function can react by setting some pending flag to show some spinner or loading indicator for example.
  2. An outcome action, when the side effect is carried out and we get an eventual response : either a success or an error. So we’ll have to create a separate action for each possible outcome.

Login Component : Iteration 4

First, the Login action has been split into 3 actions as we described above : a start action and 2 possible outcomes.

Next, the onSubmit is called now with the current state as an additional parameter. Then both state and dispatch are passed down to the ‘doLogin’ function which triggers the API call.

doLogin first fires the start action, which is handled by update to display the “Please wait…” status message. Then makes the API call which returns a promise : either resolved with the success message or rejected with the login error. The promise response is handled by firing the appropriate outcome action. Which in turn is handled by update to notify the user via the status field.

This method has the advantage of being simple. It doesn’t add much boilerplate to our component while still keeping the update free of side effects. We’ll see next how this method fit in a nested hierarchy.

The App component

We’ll now expand our requirements : instead of showing a success login message. We’ll redirect the user to an appropriate page.

Create a new file App.js to hold the content of our new component.

We’ll go with the content step by step. First we define the state shape and its actions

Our state holds a page dictionary, the current user name, the current page and, since the App component will embed the Login component, we maintain the current Login state.

We define 2 actions : one for switching to the appropriate page once we get a successful login and another to route Login Actions to their destination.

Next the view function

If the user has been successfully logged in, we show the appropriate page (Guest or Admin). Otherwise we show the Login page. Note how we pass onLogin callback : the Login component will call this function in the case of a successful login with the user name. onLogin, in turn, fire the ShowPage action passing it the same username.

The update function

ShowPage is simply handled by setting the user name in the state. As we saw in the view, the user name will be used as a key to access the appropriate page from the pages dictionary.

UpdateLogin handler simply routes the inner action to Login and save its new returned state.

We’ll still have to define the code for the Admin and Guest pages. For our example, they are simple passive views that display the current user

Admin.js

Guest.js

And don’t forget to update the code for the Login component

And that’s it! we can finally hit npm run build to check the results.

And voilà! You have now a skeleton code for a simple router : You can enhance it for example to make the App component route actions for Admin and Guest. Thus allowing the child pages to be full reactive components (hint: add currentState field and an UpdatePage action). You can also hook up to the hashchange or popstate event of the window to trigger the ShowPage Action.

I’m going to stop here to get some sleep. Next post we’ll see the Elm solution to address side effects. Good night.

--

--