Architecture

React Hooks and Ember.js

Michael Klein

· 10 min read

This follow-up on the comparison between component patterns in React and Ember.js discusses the concept of React Hooks and if the same concept can be applied to Ember.js when creating clean component abstractions.

As a follow-up on my post on React Component patterns in Ember.js , I want to take a look at React Hooks and compare them to component abstractions that the Ember.js framework provides. Usually when React Hooks are discussed one gets the notion that they are a very powerful concept that enables developers to create elegant abstractions and solve “hard” problems. Mostly people talk fondly about the concept of Hooks so I wanted to find out what they are about.

Disclaimer

This is not a post that is meant to offend anybody that is working in the React ecosystem nor is this post meant as a hype post for the Ember.js framework. As discussed in a recent blog post of mine I think it is interesting how different client-side framework communities solve common problems when implementing single-page applications. I looked into the topic of React Hooks from the perspective of a long-year Ember.js developer and treated my research as a learning opportunity about a framework that is foreign to me.

The problem Domain - Composing Abstractions

When implementing complex client-side applications you want to build up your User-Interface in a systematic way by reusing the same abstractions all over your application.

Usually people only think about Design Systems when it comes to reusability in client-side applications but presentational UI-components aren’t the only things that you want to make reusable. All across your application, there are common behaviors that you want to use over and over again - like triggering an async interaction or showing a notification based on a particular user-interaction - and you want to make these patterns reusable as well.

So coming up with good abstractions in single-page applications doesn’t just mean creating reusable presentational components like buttons but also to make it easy to reuse certain stateful logic across your application.

Unfortunately finding good abstractions and composing them together is challenging and sometimes the framework you are using makes it even harder:

Before Hooks for example, according to the React core team , React didn’t provide good abstractions to share stateful logic:

React doesn’t offer a way to “attach” reusable behavior to a component (for example, connecting it to a store). If you’ve worked with React for a while, you may be familiar with patterns like render props and higher-order components that try to solve this. But these patterns require you to restructure your components when you use them, which can be cumbersome and make code harder to follow.

So, are Hooks the only good way to share abstractions across your React application? And more importantly, how does Ember.js compare?

Example - Abstracting Asynchronicity

One of the things that nearly all client-side applications have to deal with is asynchronicity. To spare our users a long wait-time when booting up the application, we often times defer to loading data only when the users actually need them or we send async requests to our backend-API when we want to persist data that our users put in via a form.

We want to make it easier to deal with asynchronicity in our application, so lets imagine we got the idea to create a reusable abstraction that “solves” this issue for us. To do this we want to create an abstraction that holds the state of an async interaction and lets us trigger an arbitrary async behavior somehow.

To make the following abstractions with React and Ember.js most comparable I decided to model the asynchronous behavior of our application with a statechart .

This way we can be sure the components behave the same way and compare the actual implementations better. We will be using the popular XState library to model the statechart. This is the statechart that we will be using across this post:

The statechart isn’t doing too many interesting things. We are dealing with four different states in our implementation of our Async-component - idle, busy, success and error. Whenever we are in idle we can trigger an async-trigger function that we need to pass into the XState Machine via the machine’s context. This request can either resolve or reject which takes us into the success- or error-state. When the request resolves we will trigger an onSuccess-handler, when the request rejects we will trigger the onError-handler.

React

First, we will take a look at how we can implement a reusable component that models async behavior with React. We will compare two versions - one that is using React.Component and one that is using React Hooks. We do this because the official documentation of React introduces Hooks as a superior way to make stateful logic reusable and we want to test this hypothesis.

Both versions display the state that the Async interaction finds itself in and a button that can be used to trigger an async interaction. When clicking the button the interaction will randomly resolve or reject. You can try out both implementations in the following CodeSandBox:

As you can see both versions of the Async-component behave the same way. This leads us to the first important point in this blog post:

Sharing reusable logic via component abstractions in a clean way is possible when relying on Hooks as well as it is when using React.Component.

Let’s dive into the implementations:

React.Component

To make the state of the async interaction available to the template layer we will make use of the Provider Pattern .

First we need to create an Async-component that starts an XState-interpreter when it gets rendered to the page. Via React’s Context-API we create a Provider/Consumer-pair that provides the state-property of the component to other components that want to use the Async-behavior.

Overall this adds quite a bit of boilerplate because of the way the Context-API works but to me doesn’t read too bad, isn’t hard to reuse or hard to follow.

Let’s see how React Hooks compare:

React Hooks

To create a custom useAsync-hook we create our own abstraction on top the useMachine-hook that @xstate/react provides. Creating your own custom Hooks by composing exisiting ones is encouraged and one of the selling points of React’s Hooks abstraction.

As you can see creating a custom Hook is pretty simple. We use the useMachine-hook, setup our XState-interpreter and then return an object that we can then use in our application code to handle async-behavior.

To do this we have to create a component that uses the use-async-hook though. At some point, something has to call our hook and it doesn’t seem to be possible to do this from the “template”-layer directly like it was possible with the Async-provider component.

To me, the implementation with React Hooks conceptionally doesn’t look much cleaner than the React.Component one. Both versions abstract the stateful logic of an asynchronous interaction away but I don’t see why doing this cleanly wouldn’t have been possible already with a dedicated stateful object when using React.Component without the introduction of the Hooks concept. Although often introduced that way, Hooks don’t seem like a concept much easier to understand than native javascript classes and some people in the React ecosystem seem to agree .

Ember.js

@glimmer/component - A stateful component

Ember.js doesn’t provide a hook abstraction so to model async-behavior we will use a similar approach as we did with React.Component and create an Async-component that will hold the state of our async-interaction.

idle

When looking closely, the implementation in Ember.js is a mix of the two different implementations in React land. We use a hook-like abstraction useMachine that the ember-statecharts -addon provides but then profit of the fact that we are dealing with a stateful component instance that can hold properties and functions of its own.

useMachine in this example doesn’t create anything special but an object that holds the XState interpreter state . We assign this to a property of the component instance and can then make use of Ember’s autotracking feature and add getters like isIdle that will automatically update when the state of the statechart changes.

To make the state of the Async-interaction available to consumers we use Ember’s yield-functionality to provide block parameters to the component block. The caller of the component can then decide which properties that the Async-component provides they are interested in and use them accordingly.

Sidenote: Hooks in Ember.js? - @use and Resource

At the time of writing this post - there’s an open RFC for Ember.js that suggests to add functionality to Ember.js that might look similar to React Hooks. With the @use-decorator and the Resource base-class you will be able to implement stateful abstractions that can be @used by other objects in your application.

The important part about @use in the context of an Ember.js application is that a used Resource will be tied to the lifecycle of the object that is calling @use. This is nice to have when writing complex Ember.js applications but not a change necessary to work with stateful objects in your Ember.js codebase that make it easier to compose behavior.

So in short - @use and Resource are not Ember.js’ answer to React Hooks and quite frankly I personally don’t feel like React Hooks are such a revolutionary programming paradigm in the first place. To me, Hooks seem more like a band-aid over shortcomings that React has conceptionally. In React it is pretty verbose to share state and stateful logic between different parts of your application when using React.Component because you have to rely on React’s Context-API to do so. Because sharing state via a Provider/Consumer pair is so verbose it is understandable that Hooks are a welcome concept to a lot of React developers but conceptionally they don’t seem to me like a paradigm that is generally superior to what other frameworks like Ember.js provide.

Summary

Again, as in my React Component Patterns -post, I am happy to report that it is possible to create as clean component abstractions with Ember.js as it is with React. The suggested Ember.js implementation even has advantages over both React implementations.

First, it is much less boilerplate to write than with the React.Component implementation. There’s no need to create a Context because Ember.js’ yield-feature allows you to share component properties and functions with the template-block in a simple way. Secondly in contrast to React Hooks, Ember.js makes it easy to build up reactive component abstractions with its auto-tracking of getters whereas with React you have to update the state yourself whether you were using React.Component or React Hooks.

I feel fellow javascript developers, because of React’s prominence, often default to the idea that React leads the way in innovation when it comes to clean abstractions in single-page application development. When investigating a bit more thoroughly I don’t feel this to be a fair assessment. At least not when comparing with Ember.js.

React Hooks, while kind of an interesting concept, simply don’t feel as exciting to me as they seem to feel to React developers because they solve problems that don’t exist when working with Ember.js. React Hooks are certainly a breath of fresh air when you are used to the need of creating a lot of boilerplate to do bread and butter stuff like sharing state in your component tree but that has never been an issue with Ember.js in the first place:

  1. Ember’s yield-functionality allows you to pass arbitrary data around that callers then can use as they please. With that, it is easy to create provider components that implement stateful behavior and make it available to other parts of the application.

  2. When the need to access global application state arises, Ember.js provides services that you can inject into other parts of your application via dependency injection.

  3. Creating abstractions that you can compose and use from other abstractions also doesn’t seem revolutionary to me. It has always been possible to create stateful objects in javascript that follow the single responsibility principle and to compose them. You don’t even have to know about the Rules of Hooks when doing so.

Overall I’m very happy about how well Ember.js Octane compares to React when working on the same client-side application challenge. The framework provides developers with the necessary tools to create concise component abstractions without the need to write a lot of boilerplate code and makes it easy to share stateful logic across different parts of your application. The interesting thing about this post’s exploration to me has been that the abstractions used for the Ember.js component have been around in the Ember.js framework for a long time. There’s no new exciting feature that was added in the recent past that finally made it possible to achieve with Ember.js what React promotes with the Hooks pattern. This might be of interest to people that never would have considered Ember.js for their next upcoming projects. I guess sometimes it is a good idea to compare frameworks on real-world scenarios and don’t decide on your technology stack based on what’s the latest trending opinion on HackerNews.

Please share your thoughts about this post with me on Twitter - I’m always interested in hearing about your experiences when working with React in comparison to Ember.js and your opinion on which one of the two you feel is easier to use.

If this post has sparked your curiosity and you consider Ember.js a possible fit for your next project don’t hesitate to . We are here to help teams build ambitious applications and can help you prototype solutions so you get an idea about which library or framework fits your problem domain best.

Schedule a call

We are here to enable your team to deliver ambitious applications. Let's discuss how we can help.

European flag

© 2023 effective ember All rights reserved.