Building one-way data flow in Elm may be a better way than React

In this article, we will go through some advantages of Elm in comparison with React and Redux. I noticed them some time ago and decided to share them with you.

Motivation

As I challenged myself to learn Elm, I fell in love with it and programmed my bachelor thesis in it. Elm implements the same paradigm as components in React and keeping the state in Redux. The idea of the single source of truth. Why Elm comes nowadays to the foreground? If Javascript already provides us with this paradigm? Why learn Elm? One of the answers for the former may be, that in the past our computers were not able to run a functional language in a reasonable time. By reasonable time we mean the time, which today’s user accepts to wait and doesn’t leave a webpage.

In the past, the functional languages were used to calculate complicated and time-demanding mathematical calculations. For example, Lisp existed since 1958 and became favourite programming language for AI research. However, it wasn’t efficient enough to run on a general-purpose computer. That led to the creation of Lisp machines.

Functional languages usually implement a feature called immutability. Immutability is such a feature when a programmer isn’t allowed to mutate/modify a given object. That means a language creators didn’t implement any built-in functions which would allow a PROGRAMMER to change the object. However, that has nothing to do with the way, how the compiler changes the object.

  1. The compiler of a functional language doesn’t usually modify the existing object but create the copy with the changed property. If this is the way how a compiler implements immutability, then the demands on our memory and processor are high.
  2. In these days compilers may implement immutability efficiently. To save the resources, it is insufficient to copy all of the elements. Therefore the compiler changes just a given object.

Nowadays we finally have sufficient resources to support immutability in both efficient and inefficient way. Hardware and a software support functional languages not only when performing mathematical calculations but also when running web applications. We need to render such webpage in the shortest possible time and that is nowadays possible.

Main reasons which made Elm a better solution for frontend than React

I see the main advantage of creating an Elm app in the statelessness and immutability. There are no stateful components but pure functions which return always the same value depending on the input. The feel of the development process is absolutely functional. I also noticed other reasons, why Elm does one-way data flow better than React with Redux. In the first place, we don’t need to use 2 separate libraries to keep the state (Redux) and render it (React). Consider also the arguments below, as well.

A child can change the state of a stateful parent directly

Look over this piece of pseudocode. There is a stateful component which passes its bound function to its child. This child can change the state of the parent without triggering an action.

The problem is, we can send the parent’s reference to its child and modify the parent’s state from its child component. Although the documentation of React library says, that data can flow in only one direction. There is written: “Remember: React is all about one-way data flow down the component hierarchy.“. Of course, we can choose not to do that. In my humble opinion, if we want to use the idea of one-way data flow in our application, then we should do it properly.

There is only one way to update the parent’s state from its child in Elm – to trigger an action. There isn’t even any reference ( this ) which could refer to the parent and therefore we can not pass it down to the child.

Insufficient treatment of proptypes leads to runtime errors

Another problem with the piece of code above is in the proptypes.  Javascript allows us to define them in a way, that none of them is required. Sometimes, less experienced develpers don’t define proptypes at all. Maybe on purpose, maybe not. Insufficient treatment of properties can lead to a lot of harm.

An inattentive programmer can compare if (items.length === 0) and he may not notice, that items aren’t required at all. He may get runtime error because he is accessing length property on an undefined value. Not to mention, when proptypes aren’t defined at all. In that case, items may be of type which doesn’t necessarily have the length property. Consequently, runtime errors will occur.

Runtime errors in Elm

The most common error in Javascript is a type error. Since Javascript is an untyped language we may call an undefined object as a function, access undefined property in an object or use a null as an object of string type. In all of the mentioned cases, we get a type error.

Because Elm is a typed language, it deals with type errors in a very elegant way. While compiling, the compiler finds out all of the types of actual function arguments and checks if they fit the function definition. If the function doesn’t have the definition given explicitly, Elm compiler derives the definition thanks to the type inference feature. If Elm compiler detects that some arguments don’t fit the type definitions, it throws a compilation error and informs us with a friendly error message what went wrong. The error messages are so friendly that after they explain where is the problem, they even suggest us how could we fix the problem.

One big goal of Elm is to have no runtime errors.” In Elm, we may not encounter a runtime error for a very long time. However, there are still cases when it can occur. A runtime error occurs on Javascript’s side when communicating with Elm through ports. I encountered another runtime error and that when accidentally trying to render components in an infinite loop.

Consider we have a recursive type definition as in my example application built for the purpose of this article. Elm compiler reserves some memory for such an object. But the compiler can not estimate exactly what will be the maximum memory size for such an object in runtime. Therefore, if we create a bigger object than Elm compiler estimated, we get a runtime error.

Another runtime error occurred when I used contentEditable property on a div element and subsequently trying to set its value to a given string. I solved the problem when I used defaultValue instead of value. There were many other conditions under which this error occurred. Its message was something like “Uncaught TypeError: domNode.replaceData is not a function“.

Proper handling of an initial state

Consider this state: {data: [], loading: false}

This data model explicitly says: In the beginning, when there was not an initial data fetch, we usually show the user such a message as “Sorry user, there is no data yet. Let’s create some.”. But, what if we just haven’t loaded the data from the server yet? We can’t show the mentioned message.

There are lifecycle methods in react component which may help us, but they won’t solve the problem. We fetch the data in componentDidMount and receive them in componentWillUpdate, but this doesn’t guarantee that the data will arrive before render.  There is nothing left than rendering the message “Sorry user, there is no data yet. Let’s create some.” Let’s add another variable wasDataFetched to our model: {data: [], loading: false, wasDataFetched: false}

If {wasDataFetched = true, data = [], loading = false} , then we are sure that the data were fetched and “Sorry user, there is no data yet. Let’s, create some.” message can be shown. In order, we can determine properly that the next request can use this variable too, be sure, to set wasDataFetched to false in the next state. If you think about this solution, it doesn’t fit our needs. There is an issue because there has to be at least one state between requests where wasDataFetched is set to false.

Another issue occurs, if the next fetch will not be the initial and in the previous fetch we return an empty array. The state now looks like this: {data = [], loading = false, wasDataFetched = false}. But this state could also describe the following: Since the application is running, we haven’t fetched ANY data from the server yet. This is not right. We just encountered one state which demonstrates 2 different use cases. We should probably add a new variable to the state which tells whether this is initial fetch or not. So the final state which solved the mentioned problem is as follows:

data = {data: [], loading: false, wasDataFetched: false, isInitialFetch: false}

Elm solves this problem in a very neat way. It creates special types for every state – not asked, loading, failure and success. This article explains how is the idea implemented.

Of course, we can implement this idea in Js.

data = {data: undefined, loading: false}

But we will also break Javascript’s best practises. If data is fetched, it will replace undefined. Jolly good fellow! Who wants to have undefined in the app and risk getting a type error? We can simply ask if (data.length === 0) without risking getting a type error.

I hope you try Elm as a frontend solution one day and will like the purely functional way of programming. The next article will be about some good practices in Elm. I learned those in the last year and found them useful. We are going to make a simple single page app and demonstrate some important details which should Elm developer master.