React? No thanks I'd rather Riot! (JavaScript Front End Development Alternatives)

React? No thanks I'd rather Riot! (JavaScript Front End Development Alternatives)

Show table of contents

In this article I'll explain why I'm not using the React Javascript front-end development framework. You'll also learn why you shouldn't use it either and I will provide a great alternative.

Whenever I develop anything I'm more concerned with DevOps, thus the backend part of things.
This means that I'd rather spend my time refining PostgreSQL queries, implementing high availability and fault tolerance than wasting time with the javascript frontend bloated nightmare.

This way I can scale down from 10 servers to maybe 3 and reduce TCO costs.

React itself is too bloated, contains too many fluff and way too complex for most people's needs.
The JSX (XML syntax to HTML) makes it awful. It's learning curve takes a long time to get used to and making your code kind of incompatible if you ever decide to switch framework.
The biggest problem is that it has become a pure marketing fad. Managers and developers alike love marketing fad more than productivity.

Being made by Facebook doesn't give it a stamp of approval. Sure, they release quite interesting tidbits.
React prior to 15.0.0 used to be 600 kilobytes big. The minified version used to be 150 KB. Around 15.4.0 they moved the fluff to react-dom which still makes HUGE.

What alternatives do we have?

There are various alternatives which are way better in terms of usage and maintainability.

Pure JavaScript

The first alternative is to use pure JavaScript. I've said it before, jumping in and using a framework is bad practice. First as a developer you need to understand the problem and have a solution with the basics provided by any programming language.
I've made it a habit to dive in and try to create small minimum viable projects with functions in pure JavaScript. (Mabe you should to?) This makes it easier to share code, improve and adapt based on the changing needs of a project.
It won't make full rewrites of code difficult and time consuming.

The first thing is to make sure you can do the basics in pure JavaScript. JavaScript has evolved in the last 15 years so much that you don't really need libraries like jQuery for simple things.
You can implement most of the basics of React with JavaScript Promises and the Observer pattern.

Yes, with fetch and promises you can nearly eliminate the need for a front end framework if you're smart enough.

What about other frameworks?

Well, there are tens of different front end frameworks including Vue, Elm, Mithril, preact.. They all try to solve the same issue

I've experimented a bit with some of them and if I wasn't able to build a real world MVP example in less than 60 minutes then they're too bloated and complex.

Riot.js did draw my attention.

Riot.JS to the rescue

I'll let the riot.js compare page explain the differences between React and Riot so you can see for yourself.

What really makes it special?
You just write custom tags in HTML and combine these tags with JavaScript. The syntax is extremely friendly and easy to use.
I mean, if you know HTML and a  javascript you're set and ready to go!
It has many powerful shortcuts and it's interpolation is so facile that anyone can learn it in a matter of minutes!

Just reading through the https://riot.js.org/guide/ and looking at the API will give you all the info you need to start working. Compare this with the complexity of React.

In the end you can save these tags to a file and either compile them to pure JavaScript with the riot compiler or use the Riot Compiler in the browser so it does this automatically for you.

Riot.JS + Phoenix = Love

JavaScript Fetch

The following is a simple example I extracted from the backend part of this Blog system. It's meant to create a simple "image gallery" in which you can select an image for a blog post. It's straightforward and clean.
It fetches the data with JavaScript "fetch" from the Phoenix backend.

form.html.eex

	<!--. some html here -->
div uk-grid>
  <div class="uk-width-1-3@s">
<images-modal></images-modal>
  </div>

    <%= text_input f, :heading_image, class: "uk-input uk-width-2-3@s",  placeholder: "Heading image" %>
    	<!-- The above code creates this input
	<input class="uk-input uk-width-2-3@s" id="post_heading_image" name="post[heading_image]" placeholder="Heading image" type="text" value="">
	-->
	
<!-- some html ...
..... At the end of the HTML page before the ending of the body .... -->

<script src="<%= Routes.static_path(@conn, "/js/riot%2Bcompiler.js") %>"></script>

<script src="<%= Routes.static_path(@conn, "/js/tags/images-modal.tag") %>" type="riot/tag"></script>

   <script>
       riot.compile(function() {
         //riot.mount('*'); // when using multiple tags on the sme page:)
         riot.mount('images-modal', {button_text: "Select Heading Image",
        title: "Uploaded Images", input_id: "post_heading_image",
         fetch_url: "<%= Routes.page_path(@conn, :uploaded_images) %>" }  )  
         route.start(true); //Starting our routing
       })
     </script>
 </div>

form.html.eex is the file containing the html+Elixir form creation
We just specify the <images-modal></images-modal> tag, include the riot+compiler.javascript and the tag file which we will work on shortly.

/js/tags/images-modal.tag

We have defined a tag <images-modal></images-modal> which contains:

  1. A button with a name which on click will
  2. open a modal containing all of our images (we wont add pagination at the moment although it's fairly easy to extend it for pagination)
  3. Upon clicking on an image we'll select it's name and update the input box with the image's name
  4. we'll load the image thumbnail below as a confirmation
<images-modal>

  <button type="button" uk-toggle="target: #images-modal"
     onclick="{get_images}" class="uk-button uk-button-primary">{button_text}</button>
<img src="{image}"  uk-img/>

  <div id="images-modal" class="uk-modal-container" uk-modal>
      <div class="uk-modal-dialog uk-modal-body">
          <h2 class="uk-modal-title">{ title }</h2>
          <button class="uk-modal-close" type="button"></button>

<!-- uk-lightbox="animation: slide" -->
        <div class="uk-child-width-1-5@l uk-child-width-1-4@m  uk-child-width-1-3@s"
          uk-grid>
        <div     each={image in images}>
            <a class="uk-inline uk-modal-close" href="#" onclick="{ set_selected_image }"
               data-fullimage="{image.full_image}" data-caption="Caption 1">
                <img src="{image.thumb}"  alt="">
            </a>
        </div>
      </div>

      </div>
  </div>

<script>
//The options from "opts" are assigned to "local" variables
this.title = opts.title
this.button_text = opts.button_text
this.images = opts.images
this.fetch_url = opts.fetch_url
this.input_id = opts.input_id
this.current_image =""

  get_images(e) {
    e.preventDefault()

    fetch(this.fetch_url)
    .then(response => response.json())
    .then(data => {
      console.log(data)
    //  this.images = data.files
      this.update(data)
    })

    return false
  }

  set_selected_image(e) {
    e.preventDefault()
    image = e.currentTarget.dataset.fullimage
    console.log("Image is " + image + "caption " +
    + " dataset " + JSON.stringify(e.currentTarget))
    document.getElementById(this.input_id).value = image;
    this.update({current_image: image})
  }

</script>
</images-modal>
<!--
	# vi: ft=html
-->

 

First when our page gets loaded it will populate all our variables with the { } interpolation. Naming our button and modal.
We could at this point specify the newest uploaded images via the images option on form.html.eex.

Then whenever  we click on the button it fetches the specified url and parses the JSON response.
It then proceeds to update everything with this.update(data).
What this does is it takes the JSON data and automatically recalculates the whole modal.
The each={image in images} does a for loop and fills in the data for the underlying html div. This makes looping beautiful by avoiding ugly code.

This is a simple and concrete example. Using Riot.JS plus it's compiler which minified is around 36 KB. The rest of the code is in pure JavaScript.

There are many more front-end improvements to the user interface
We could have made the input hidden and loaded the image as a header above. Sure, all these customizations can be done as the 2nd version.

page_controller.ex

The uploaded_images function is just some Elixir code in the Phoenix web framework of my blog.

This function which will list all files from a certain prespecified upload path example /home/production/public_html/upload/
It will then remove the path so we're left with the image's relative path /images/blog/02/cool-image.jpg
And it will filter all images which contain "-thumb" in their names.
This way we'll map the original image without -thumb and the full image name.
Then it sends the json response back.

Whenever I upload images to the server they automatically get their own thumbnail and the location is saved in the database. I could have easily selected them from the database.
But maybe sometimes you want to manually upload images.

    def uploaded_images(conn, _params) do
      file_upload_path  = Application.get_env(:burebista, :file_upload_path)
      image_files = FileExt.ls_r(file_upload_path)
      |> Enum.map(fn x -> String.replace(x, file_upload_path,"") end)
      |> Enum.filter(fn x -> String.contains?(x, "-thumb") end)
      |> Enum.map(fn x ->
        %{thumb: x , full_image: String.replace(x, "-thumb","")}
      end)
      json(conn, %{images: image_files})
    end

 

Putting it all together demo

I'm editing a test page I've created which already contains a link to an image.

After clicking on the "Select heading image" a modal pops up with a part of the images i had on my local testing system

Clicking on any image will select it and we get the following page

Websockets

Since the Phoenix framework has great support for channels and websockets we can easily extend it to use Riot.JS.
I've also used Riot.js with websockets in a more complex project for a web based game.

There are some extra things to making this work with WebSockets. I'll cut 90% of the fluff to show a basic example.
Again, first  we'd need to mount the tag and send the data etc.


The tag file

Connect to websockets & process a movement

<game-map>
<div  style="background: white; padding:10px">
	<h2 class="">  { title }            </h2>

			<p class="alert alert-success" role="alert">{ success }</p>
			<p class="alert alert-info" role="alert">{ info }</p>
			<p class="alert alert-warning" role="alert">{ warning }</p>
			<p class="alert alert-danger" role="alert">{ error }</p>
	<section class="row">

		<article class="column column-40" style="border: 3px solid black;"   hide={["exhausted","dead"].indexOf(page) > -1} >

			<div class="row buildings" each={ building_row in buildings }>
				<div class="column" each={ building in  building_row }>

					<a  onclick={ building[1] != "here" ? parent.parent.move : null  } href=""  data-maplocation={ building[1] } class="building  { building[1] == 'here' ? 'button-green': ''}"> John Mallot's { building[0].name } ({ building[0].x },{ building[0].y })   </a> 
				</div>  
			</div>
			Player Stats for <strong>{ player.name }</strong> ({player.player_status})
			 <span hide={ player.infection } class="text-success"> You are HEALTY  </span>
			 <span show={ player.infection } class="text-danger"> You are INFECTED  </span>

			 <a class="button" href="#" id="startTutorial" show={player.max_exp < 100 }>Start Tutorial</a>
			 <img src="/images/timeofday/{player.timeofday}.png" id='timeofday' title="{player.timeofday}">
			<div  each={progress in [
				[player.health,player.max_health,true,'Health: '],
				[player.energy,player.max_energy,false,'Energy: '],
				[player.hunger,100,false,'Hunger: '],
				[player.thirst,50,false,'Thirst: '],
			  {current: player.currentweight,max: player.max_weight,inverse: false, text: 'Carry weight: '},
				]}>
				<progressbar progress={progress}> </progressbar>
			</div>
		<!-- 
				Some more HTML code combined with the Riot.JS syntax ...
		 -->
		</article>
	</section>
<div>
<script>
/*
.... Riot.JS initialization and other JavaScript code 
*/		
move(e) {

	e.preventDefault()
	//	e.stopPropagation()
	map_location = e.target.dataset.maplocation 
	this.channel.push(`move`, {location: map_location}).receive(
		"ok", (reply) => {
			console.log("Got reply.. moved", reply)
			this.update(reply)

		}
	).receive("error", (reply) => {
		console.log("Got error.. unfortunately.. ", reply)
		this.update(reply)
		}
	)
	console.log("Requested move to " + map_location );
	e.preventDefault()
	// return true;
}

this.on('mount', function () {
	console.log('Chat loaded ' + opts.name);
	console.log('Room Channel  ' + this.room);

	let channel = window.createSocket(this.room)
	this.channel = channel


})
</script>
</game-map>

In the above example we have the buildings object which contains a map of the surrounding location a player is located at.
On mount we connect to the websocket connection (provided by some Phoenix helpers).

Now when the user clicks on a new location we get can get quite different responses from the server backend.
Most of the time it's a success "ok" return. The reply object will contain all the new settings for our game, including Health energy, any events, any message and of course a new redrawn map.
It all gets updated thanks to the this.update(reply). Riot.Js loops through the changes and updates them accordingly. We don't have to do anything at all.

Now what's cool about this is that certain aspects of the page will get shown or hidden if a certain object is set to true or false. Take the infected/healthy tags as an example.

Heck, my game-map.tag has multiple tags which it makes use of. One example being a custom simple progressbar
You can view the full game-map.tag here.

Conclusion

I haven't really shown examples of Riot's route module or other fancy stuff. You can head over to the guide or read the API

Riot.js is really fun to use since there is an extremely simple API to learn. It's intuitive and easy to implement in any workflow.

Consider ditching all other front end bloatware, especially React. 97% of the time Riot.JS is more than enough.

Subscribe to my newsletter


What are your thoughts?

All comments are moderated and must adhere to the terms of service.

You might enjoy these similar articles:

Subscribe to my newsletter!