Architecture

React Component Patterns in Ember.js

Michael Klein

· 12 min read

Recently I came across a blog post that discusses several React Component Patterns. In this post, I want to explore if and how these component patterns can be applied to Ember.js and also how Ember.js compares to React when using those patterns.

Although I am in no way an expert on React, it is always interesting to see how other javascript framework communities tackle challenges when implementing single-page applications. I can’t speak with authority to the quality of the implementations of the discussed component patterns but from what I have seen in open source code and read about component patterns in React these patterns seem to be used in the wild. And because the challenges we face in client-side application developement are the same across ecosystems, the ideas behind them are interesting to developers that work with other frameworks as well.

Disclaimer

This post is not meant as a hit piece on React or a post that hypes Ember.js in contrast to it. But people that have the notion that Ember.js is this super complicated framework and think that React is much more approachable might be pleasantly surprised how modern Ember.js compares to React.

The examples shown in this post on the React side are for the most part using React.Component. The ideas don’t change that much if you are using Functional Components though. To be consistent I opted to use only one option of implementing React components from the pattern blog post I found. Please refer to the post itself to see examples of how you can implement the patterns with functional components.

Patterns

The post about React Component Patterns that I want to discuss here introduces three patterns that are meant to “help with problems that arise in large React applications”.

The patterns introduced are:

  1. Compound Components
  2. Flexible Compound Components
  3. Provider Pattern

I will go through the idea behind those patterns and try to reimplement them in Ember.js.

Compound Component pattern

Compound Component pattern in React

Compound components are used when you want to create a component abstraction where a parent passes state down to its children. The post on React Component Patterns introduces a component similar to the <select>-element in HTML as an example where you have a top-level select and then use several option-elements to create a more complex experience.

In the provided example we are implementing a <RadioImageForm>-component that manages the state of a selectable value where the consumers of the component can customize the displayed UI via a <RadioImageForm.RadioInput>-component that displays a radio input that shows an image that the user is able to click to select a value.

The challenge that we want to solve with this pattern is that we want to provide an API that makes it easy for the consumers to use the RadioImageForm without the need for them to know about the internal implementation of it.

To do that we need to pass down the state of the currently selected value and the function that we want to trigger on the parent when the user clicks the RadioImageForm.RadioInput.

In React you can achieve this by implementing a render method that looks like this:

In this example, we map over all the children of the parent and then manipulate each child with React.cloneElement, basically passing additional props that the consumer didn’t specify.

We also create a static property RadioInput that renders the RadioInput and declare a contract that the consumer needs to follow - i.e. the consumer needs to pass in label, value, name, imageSrc and key.

To finally use the compound component in application code we can use the following code:

Compound Component Pattern in Ember.js

Reimplementing the Compound Component-pattern in Ember.js is straight forward. We create a parent component that implements onChange and holds the currentValue. To make it possible for the consumer to define the RadioInputs we can yield the RadioInput component and use the component -helper to pass default properties to it that the caller can override:

Ember.js Component Patterns - Compound Component
Parent Value:

In our example, we make use of Ember.jsyield-functionality and the block parameters -feature that yield provides. We can yield arbitrary values to the component block and the consumers can then decide what yielded values they want to use. It can be helpful to think of the yielded values as functions of the yielding component that can be called on demand by their consumers.

In our example, we yield a RadioInput-component that we pass default parameters to and the currentValue of the RadioInputForm. To create the entire selection UI we need to iterate over the data that specifies the available items and render a RadioInput for each one of them.

Compound Component summary

The compound component pattern can be used as well with Ember.js as it can be used with React. It is arguably simpler to use with Ember.js than it is with React because you don’t have to know the intricacies of React.Children and React.cloneElement. In contrast to being responsible for rendering its children directly, Ember.js components can yield predefined values that can be used by their consumers in the component block. Ember’s way to yield properties to the component block is flexible and gets rid of an issue that the React implementation of the Compound Component pattern has.

In React’s implementation, you can’t add other content than a RadioInput into the block - you can’t change the layout of the component without changing its implementation because the layout is directly coupled to the way React.Children works. The implementation expects every child of RadioInputForm to be a RadioInput because the RadioInputForm is responsible to render its children and changes what is being rendered when calling its render-method.

Ember.js in contrast is more flexible. The parent component provides callable values to the block that the consumers can then use as they please. That’s also the reason why we can access the currentValue of the RadioInputForm directly. This makes it easier to compose components than with React and leads us to the next Pattern:

Flexible Compound Components

Flexible Compound Components in React

Sharing state between parent and child components isn’t trivial in React. To do this flexibly - i.e. regardless of where the children are rendered in the component tree - you can make use of React’s Context API .

Here we are creating a Context - basically a state bucket - that we then have access to in child components via a Provider and Consumer pair:

It is important here that you be careful how you pass state to the Provider. Because React is rendering all the time you have to remember to always pass this.state and not to pass the state as a new object like <RadioImageFormContext.Provider value={{ currentValue: this.state.currentValue, onChange: this.onChange }}>. You have to do something similar with useMemo when using functional components for the same reason - please refer to the Flexible Compound Components w/ Functional Components CodeSandBox for details.

Here’s the result of using the flexible compound components pattern with React:

Flexible Compound Components in Ember.js

Ember.js with its yield-abstraction does not share the same problems as the React implementation. Extending our example with a Submit-button is trivial.

We yield out a new Submit-component that we pass the currentValue to like we did before with the RadioInput. There are no new concepts to learn for Ember.js developers just to be independent of the rendering order of children.

Ember.js Component Patterns - Flexible Compound Component
Parent Value:

Flexible Compound Component Summary

React allows developers to create component abstractions independent of the order of children in the component tree. Ember.js makes it easier to implement a flexible component abstraction though and lets developers fall into the pit of success .

As an outsider of the ecosystem, React in this example looks very low level to me. There are multiple ways to do a similar thing - use React.Children or the Context-API- and it’s not trivial for newcomers like me to understand what the benefits of certain approaches are or if one is much better than the other why the other one exists in the first place. You also always have to be aware of how React is handling rerenders - the original blog post has multiple warnings about this across the different pattern sections.

Ember.js in contrast lets you pass values around via yield that you can think of ready to call functions inside a component block - this is pretty much aligned to what you can do with currying in javascript already and makes it obvious where certain values are coming from in the template context. I.e. rif.Submit is transparently scoped to the RadioInputForm as |rif|-section of the template.

That leads us to the last pattern that we want to discuss:

Provider Pattern

Provider Pattern in React

The provider pattern is a solution to share data across the React component tree and uses the two previous concepts - React’s Context API and render props together.

React as Ember.js is using uni-directional data flow so when combining multiple components you sometimes have to prop drill shared state from the parent level to the child components.

To get around the issue of prop drilling in React - because React unlike Ember.js with its Services abstraction does not have a built-in version of global state that you can inject into Components - you have to use React’s Context API or a third party state-management library like Redux to make the state available to components in different levels of the component tree.

The pattern blog post introduces a DogDataProvider-component to illustrate the provider pattern. This component will be responsible for loading data - in this case objects that hold data for different dogs - and provide it to several other components.

In this example, we create a Context and a functional component that holds the state of the async request of loading dog data. We then pass the state of the functional component to the DogDataProviderContext.Provider to make it available to consumers of the provider.

Next we create a custom hook that we will use to make the provided data available to other parts of the application.

Because we need to make sure that DogDataProviderContext exists in the scope of all components that try to use this custom hook, we add a check so that we don’t forget to wrap everything that needs access to this state into DogDataProvider.

When putting everything together we end up with the following:

In the example, we see that everything that will use the DogProvider-state will need to be wrapped into DogProviderState. Inside of the Profile-component we access this state and display a loading state, an error state, or the loaded data based on the state of the async request.

Provider Pattern in Ember.js

Prob drilling is an issue in Ember.js codebases as well. With Ember.js you can also create component abstractions that span multiple levels deep that need to access the state of a certain parent component. Using the provider pattern in Ember.js is thus as useful as it is in React. In Ember.js though you don’t have to necessarily create a custom component to be able to use global application state because in contrast to React the framework provides more abstractions than a Component.

To hold global application state in Ember.js you would usually use a Service . A Service in Ember.js is a singleton object that you can inject into different parts of your application via dependency injection . These services are lazy and will only get initialized when you access them for the first time.

In our Example we will create a DogData-Service and inject it into our Profile-component directly:

This achieves the same result as the React example does but with less boilerplate. Much like in our React example, we don’t need to care where in the component tree we want to access the dog-data anymore. But to achieve the same result we don’t need to create a custom hook-function or be careful to wrap everything in a component that only exists for sharing state.

Ember.js’ dependency injection also makes it easy to test components that rely on the DogDataService. In tests, we just inject a Mock instead of the real DogDataService and can test our components that depend on DogDataService with ease that way.

Provider Pattern Summary

The provider pattern is a useful pattern to use in single-page-application architecture. It can be used as well with React as it can be used with Ember.js. In Ember.js we don’t need to create as much boilerplate as we need to create when using React though. The framework comes with an abstraction for holding global application state built-in that we can inject into consumers via dependency injection. This makes it easy to access shared state and makes it possible to test components that access this shared state in isolation.

Summary

Because React and Ember.js share the same problem domain - client-side application development - it is very enlightening to look across framework boundaries to get an idea about what other ecosystems are doing to solve common problems like component composition and prop drilling.

All of the discussed patterns in this post on React Component Patterns can be implemented when using Ember.js. Although I’m a bit biased towards Ember.js the patterns are arguably easier to use with Ember.js and you need to implement much less boilerplate to use them compared to React.

To me, it was surprising how much work you have to do in React to solve common problems when it comes to creating easy to use component abstractions. In contrast to how you usually see React compared to Ember.js in casual conversation I think it will be interesting for a lot of people to look at the solutions in this post to get an idea of how developer-friendly Ember.js is in the current Octane edition of the framework. It pays off that Ember.js is build around certain conventions that help you fall into the pit of success instead of only giving you low-level primitives to solve common challenges in a multitude of ways with different tradeoffs that only seem to be obvious to people that are experts in React.

Of course, some might argue that React is only a javascript library for building user interfaces and not an entire framework to build ambitious applications with but let’s not kid ourselves. In today’s world, we are less and less implementing small self-contained widgets but build more and more complex client-side applications that implement intricate user-flows - so I doubt a UI library is the only thing that you are looking for when deciding on the technology to build your next client-side-application with.

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.