John Gozde is the tech lead for the Imply Polaris UI.
When you think of APIs, what comes to mind? REST? RPC protocols and wire formats? Syscalls?
If you’re a frontend engineer, there are domain-specific APIs you interact with every day, but maybe you don’t think of them as such. I’m talking about Components.
Every major UI framework has the concept of a component. They help us encapsulate reusable functionality and set boundaries around what is displayed and how it behaves. It’s useful to think of each component as an API unto itself, albeit one that is a subset of a larger framework-specific component API.
The framework’s API is essentially fixed and beyond our control, but what we can control is exposed to other users/developers who consume the components we build. Things like naming conventions, prop types, and broader patterns for groups of related components are entirely user-level concerns that have downstream impacts.
What follows is a set of guidelines for building React components at the level of a component library or a design system. They are non-exhaustive and represent my personal tastes, but they have been largely extracted from Imply’s internal Canopus design system.
Feel free to use this however you like – fork it, copy it verbatim, or negate every single rule. The details of the guidelines are less important than simply having guidelines at all.
Caveat: It’s more important to be internally consistent than to strictly adhere to this or any other style guide. That is, if the project you’re contributing to has an established set of guidelines that contradict these, it’s better to follow what is already in place, at least for the short term. It’s fine to change the conventions a project uses over time, but it should be done deliberately and as a team.
Use ‘onSubjectVerb’ naming convention for event props
Also known as “callback props”, these are functions that are invoked based on some asynchronous trigger event, such as a user interaction, a network response, or a change in state. Notably, this excludes functions that are invoked during the render phase.
✅
DO assign names to event props like on[Subject]Verb, where Subject is optional and Verb is usually present tense.Examples: onChange, onCancel, onTableScroll, onAvatarClick, onOpenChange, onEntryModeToggle
🤔
MAYBE break the above rule for grammatical reasons.Example: onFilesSelected – uses past-tense verb “selected” because onFilesSelect is slightly more awkward to read and say.
⛔
DON’T reverse subject/verb or use prefixes like handle, set or other verbs, even if consumers would typically pass a state setter as a value.Bad examples: setEntryMode, handleAvatarClick, changeOpen, onScrollTable
The reasoning behind this guideline is based on 1) consistency with DOM events and 2) avoiding leaky abstractions.
The setSomeThing naming convention is a particularly common mistake I see being made, usually during refactoring. E.g., some state foo that was being kept in a child component was lifted up to a parent component, so the engineer added props to the child called foo and setFoo and moved the useState into the parent.
But now we have an abstraction leak. The child should no longer be aware of where foo is stored or how it is updated. Maybe it lives in component state, maybe it lives in a Redux store, or maybe it’s something entirely different – it shouldn’t matter to the child component. All it needs to know is the current value of foo and what function to invoke when it thinks foo should change: onFooChange.
DO consider passing the originating DOM event as the last parameter when adding logic on top of a DOM event.Example: onVisibleToggle(visible: boolean, e: React.MouseEvent<HTMLElement>)
This ensures that users have the option to use preventDefault() or stopPropagation() if necessary. If a synthetic event might be triggered by multiple kinds of DOM events, you can type the event object using a type union or React.SyntheticEvent<HTMLElement>.
Avoid “is” prefix for boolean props
Boolean flags or toggles are props that accept true or false and where ={true} can be omitted when using JSX. They might be used for setting different behavior modes or for controlling some kind of binary state.
As far as naming is concerned, there are exactly two options, both of which are equally valid: “prefix with is” and “don’t prefix with is”. But since this is an opinionated guide, I can only choose one:
✅
DO use adjectives for naming boolean props that describe a component’s state.Examples: open, visible, readOnly, compact, disabled, selected, checked, required
MAYBE use present participles of adverbs when describing transient state.Examples: loading, savingBut these might also be better encapsulated in a multi-value toggle prop, e.g. state=”loading” or state=”saving”.
Please, put down your pitchforks. While that is the convention I prefer, my advice at the top, “internal consistency above all else,” still applies. It’s better for all toggles to use the same convention within a component or within a library than it is to always use this particular convention.
Prefer ‘false’ as a default value
This guideline is related to the principle of least astonishment. A boolean prop with the default value of true is redundant when specified as a flag, but the reader would only know this by inspecting the source of the component or reading the documentation on the props interface, if present.
✅
DO prefer a default value of false for optional boolean props
Therefore, it’s preferable to invert the logic such that the default value is false and specifying the flag toggles it to true.
Prefer composition over toggle props, use consistent naming otherwise
🤔
MAYBE use a verbSubject pattern for naming boolean props that describe a component’s behavior.Examples: allowDragging, hideSourceColumnField
⛔
DON’T add excessive boolean props for toggling between different behavior modes.
✅
DO prefer component composition over excessive use of toggle props.
This pattern is especially common. A new requirement is introduced that requires a slight change in behavior for a specific scenario, so the engineer adds a boolean prop to toggle between the old behavior and the new behavior. And then another requirement is introduced that requires another toggle… and then another…
After N toggle props are added, our component is handling 2N additional behavior modes, which makes it harder to predict what exactly will get rendered and how it will behave for the different combinations that exist.
Past a certain (very subjective) complexity threshold, it’s usually better to refactor this component and use composition to toggle different behavior modes rather than props. But won’t this shift the complexity to where the component is being used? Yes it will – but that complexity exists because the different usage sites have different requirements from each other, so our boolean prop was misleading us.
Prefer string-based unions for multi-value toggles
This one is fairly straightforward. Props that toggle between one of many states or behaviors should be typed as string unions, avoiding enums, objects, or other values that necessitate imports. The reason behind this rule is more for convenience and DX than anything else.
✅
DO prefer string-based type unions for multi-value toggles.
⛔
DON’T use enums, const objects, or other values that require imports.
The approach using string unions is terser, more readable, and offers the same level of type safety as the approach using enums. It also avoids extra import statements and supports refactor/rename operations with recent versions of the TS language server.
Prefer value types for props
Picking the appropriate name for a prop is half the battle; the other half is choosing its type. React runs on JavaScript and both have quirks that influence which types are more or less appropriate for props.
For instance, many optimizations in React depend on referential equality comparisons for state and props in order to avoid doing unnecessary work. This works well for value types, like strings, numbers, and booleans, but it completely falls apart for arrays, objects, dates, and all class instances.
This one language property is the achilles heel of optimization in React, which was seemingly designed for a more civilized language than what is available to us now. Records and tuples may alleviate some of this pain in a future ES runtime, but for now we need to address these issues at the component API level.
✅
DO prefer value types for props.
🤔
MAYBE inline object properties into individual props.
There are a plethora of valid reasons why we might choose an object or an array for a prop’s type. Maybe we’re rendering a list of items; maybe there’s an external dependency that accepts configuration as an object; maybe there are some props that should always be set together.
In some cases we can avoid a reference type but in others we can’t. It really depends on the situation.
Case 1 – arrays
Consider a component that renders a list of items, and the items themselves are fairly expensive to render. Using an array for the items prop is really the only choice we have (as of this writing). There are 3rd party libraries for constructing immutable lists, but due to limitations in JS as language (lack of operator overloads), it’s impossible to make referential equality, a === b, to behave like value equality.
If either the component or the items prop are memoized in some way (e.g., PureComponent, memo, useMemo, etc.), then this memoization is very easy to break for consumer components. All they need to do is specify a value like items={[1, 2, 3]} and the memoization is broken. We can even break it ourselves by setting a default value like items = [] because empty arrays are referentially unequal.
In this scenario, we have two options, neither of which are ideal. Option 1 is to clearly document that items is memoized for performance reasons so consumers should take care to do the same. Option 2 is to write a custom equality function for PureComponent (shouldComponentUpdate) or memo (areEqual) that uses shallow comparison for items. But option 2 is not available for useMemo, unfortunately.
Case 2 – non-finite objects
Another scenario similar to the above would be for object props that have a large number of properties or where the properties may have arbitrary keys. Like with arrays, our only options are to document or somehow use shallow equality internally.
Case 3 – finite objects
But consider a different scenario where we are building a component that has a default size, but that size can be changed by the consumer. Say we expose this via a size prop, typed as an object taking width and height properties:
This type is deliberately forcing consumers to choose between not specifying any size or specifying both dimensions simultaneously. But again, this component is expensive to render, so we are memoizing the whole thing or maybe just the size prop. This has a similar issue as the array case above because if a consumer provides size={{ width: 20, height: 20 }}, that memoization will be broken.
Unlike the array case, however, there is a 3rd option available, which is to inline the size properties directly onto the props interface:
Semantically speaking, this is not identical to the original props because now it’s possible to specify width without specifying height and vice versa. If we wanted to be really pedantic, we could use separate props interfaces and function overloads to enforce that both are set or not set:
function MyComponent(props: MyProps): JSX.Element ; function MyComponent(props: MyPropsWithSize): JSX.Element; function MyComponent(props: MyProps | MyPropsWithSize) { // return … }
But this is a little bit overkill and would escalate in complexity very quickly if we wanted to do the same for other prop combinations. A more practical solution would be to simply tolerate omission of either width or height and pick an appropriate default.
The key to building composable components in React is the children prop. It receives special treatment in JSX – anything between a component’s opening and closing tags is passed to it as the children prop. The component can then decide where in its tree these children should be rendered.
✅
DO accept children for composition and content whenever possible.
When building component libraries, we might be inclined to avoid supporting composition in some components, often for legitimate reasons. Take, for example, a Button component. Suppose we always want a Button to have associated text for accessibility reasons (Buttons that are rendered using only an icon would move their text property to a tooltip and an aria-label). But since the text might be rendered on the screen or it might be added to an HTML attribute, we only want to allow strings. We could build our Button props like this:
This would work exactly as we want – we can toggle between icon-only and icon-with-text rendering while maintaining good accessibility. But then what if someone wants to decorate their button text?
This would no longer compile because the children type is incorrect (JSX.Element is not assignable to string). The DOM would happily allow a <strong> inside a <button>, but we have prevented it because if we set iconOnly, the aria label would become [Object object].
So to avoid confusion, we might use an interface like this:
Now our Button doesn’t support composition at all, but users would be less likely to pass arbitrary JSX to a prop called text. This might be an acceptable compromise, but the consequence is that our Button API is no longer composable.
In this version, we allow children to be set if needed, but still require a plain text prop for accessibility. When rendering the content, we look at children first, falling back to text. This preserves composability while enforcing accessibility, at the cost of a bit of extra complexity within our component and maybe some redundancy for consumers that specify both children and text.
Prefer component clusters over “slot” props
Certain kinds of reusable UI elements are difficult to represent with a single component, especially if some degree of freedom is necessary for users. Take, for example, a Dialog component. A naive implementation might be straightforward, but as more UX patterns emerge, we might ask for more customization. Can we disable the header? Can we put arbitrary content in the header? Can we put buttons on both sides of the footer? Etc.
Though we could support extra customization by piling on props, a more natural API might be to create a “clustered” API for our component, where a component cluster is a group of components that are designed to work together and are namespaced under a single root component.
✅
DO prefer value types for props.
⚠️
AVOID using element props to define component customization “slots”.
Here is a concrete usage example of a Dialog component that offers customization via clustering:
Every component that might be part of a Dialog is namespaced under the name Dialog, which itself might be a component (we could also alias it as Dialog.Root). These components are designed to work together seamlessly, both visually and functionally, and we indicate that to consumers by grouping them under the same namespace.
Many of the sub-components in this cluster might be considered “slots” – well-defined placeholders where we accept arbitrary content. In this case, Header, Body, and Footer are slots. Some, like Body, might be required while the rest are optional.
The actual implementation of these components might vary depending on the functional complexity. Sometimes it’s a simple matter of stitching things together with CSS, but other times it might require an implicit React Context to tie things together. That’s beyond the scope of this style guide – the important thing is to avoid exposing those kinds of implementation details to consumers.
Note that the above could be designed using props instead of component clusters, which might look something like this:
There is nothing technically wrong with this approach, but for a low-level component, the API surface area is too large for the amount of customization that might be desired. An API like this might be suitable as a wrapper for Dialog after a few primary archetypes have emerged, but those would commonly live at the application level.
Avoid “render” props
This is an older component API pattern that was popular in the era of class components. Rather than using composition, components would offer customization in the form of props, which would often directly override an internal render method of the same name. E.g., renderHeader, renderFooter, renderGridBody, etc.
Render props should generally not be used for new component APIs, favoring clusters and slots (described above).
⚠️
AVOID render props if at all possible, favoring composition via slots.
✅
DO use props named render* for providing alternative rendering logic when necessary.
⛔
DON’T use children as a render function.
The “Don’t use children” rule is especially important with this pattern because a) it was fairly common at one point and b) it defies expectations of most UI component APIs. children should be broadly supported (as argued above) and should only accept renderable elements. APIs that expect children to be specified as functions or objects or anything not directly renderable should be avoided.
Other blogs you might find interesting
No records found...
Sep 21, 2023
Migrate Analytics Data from MongoDB to Apache Druid
This blog presents a concise guide on migrating data from MongoDB to Druid. It includes Python scripts to extract data from MongoDB, save it as CSV, and then ingest it into Druid. It also touches on maintaining...
How Druid Facilitates Real-Time Analytics for Mass Transit
Mass transit plays a key role in reimagining life in a warmer, more densely populated world. Learn how Apache Druid helps power data and analytics for mass transit.
Migrate Analytics Data from Snowflake to Apache Druid
This blog outlines the steps needed to migrate data from Snowflake to Apache Druid, a platform designed for high-performance analytical queries. The article covers the migration process, including Python scripts...
Apache Kafka, Flink, and Druid: Open Source Essentials for Real-Time Applications
Apache Kafka, Flink, and Druid, when used together, create a real-time data architecture that eliminates all these wait states. In this blog post, we’ll explore how the combination of these tools enables...
Visualizing Data in Apache Druid with the Plotly Python Library
In today's data-driven world, making sense of vast datasets can be a daunting task. Visualizing this data can transform complicated patterns into actionable insights. This blog delves into the utilization of...
Bringing Real-Time Data to Solar Power with Apache Druid
In a rapidly warming world, solar power is critical for decarbonization. Learn how Apache Druid empowers a solar equipment manufacturer to provide real-time data to users, from utility plant operators to homeowners
When to Build (Versus Buy) an Observability Application
Observability is the key to software reliability. Here’s how to decide whether to build or buy your own solution—and why Apache Druid is a popular database for real-time observability
How Innowatts Simplifies Utility Management with Apache Druid
Data is a key driver of progress and innovation in all aspects of our society and economy. By bringing digital data to physical hardware, the Internet of Things (IoT) bridges the gap between the online and...
Three Ways to Use Apache Druid for Machine Learning Workflows
An excellent addition to any machine learning environment, Apache Druid® can facilitate analytics, streamline monitoring, and add real-time data to operations and training
Apache Druid® is an open-source distributed database designed for real-time analytics at scale. Apache Druid 27.0 contains over 350 commits & 46 contributors. This release's focus is on stability and scaling...
Unleashing Real-Time Analytics in APJ: Introducing Imply Polaris on AWS AP-South-1
Imply, the company founded by the original creators of Apache Druid, has exciting news for developers in India seeking to build real-time analytics applications. Introducing Imply Polaris, a powerful database-as-a-Service...
In this guide, we will walk you through creating a very simple web app that shows a different embedded chart for each user selected from a drop-down. While this example is simple it highlights the possibilities...
Automate Streaming Data Ingestion with Kafka and Druid
In this blog post, we explore the integration of Kafka and Druid for data stream management and analysis, emphasizing automatic topic detection and ingestion. We delve into the creation of 'Ingestion Spec',...
This guide explores configuring Apache Druid to receive Kafka streaming messages. To demonstrate Druid's game-changing automatic schema discovery. Using a real-world scenario where data changes are handled...
Imply Polaris, our ever-evolving Database-as-a-Service, recently focused on global expansion, enhanced security, and improved data handling and visualization. This fully managed cloud service, based on Apache...
Introducing hands-on developer tutorials for Apache Druid
The objective of this blog is to introduce the new set of interactive tutorials focused on the Druid API fundamentals. These tutorials are available as Jupyter Notebooks and can be downloaded as a Docker container.
In this blog article I’ll unpack schema auto-discovery, a new feature now available in Druid 26.0, that enables Druid to automatically discover data fields and data types and update tables to match changing...
Druid now has a new function, Unnest. Unnest explodes an array into individual elements. This blog contains design methodology and examples for this new Unnest function both from native and SQL binding perspectives.
What’s new in Imply Polaris – Our Real-Time Analytics DBaaS
Every week we add new features and capabilities to Imply Polaris. This month, we’ve expanded security capabilities, added new query functionality, and made it easier to monitor your service with your preferred...
Apache Druid® 26.0, an open-source distributed database for real-time analytics, has seen significant improvements with 411 new commits, a 40% increase from version 25.0. The expanded contributor base of 60...
How to Build a Sentiment Analysis Application with ChatGPT and Druid
Leveraging ChatGPT for sentiment analysis, when combined with Apache Druid, offers results from large data volumes. This integration is easily achievable, revealing valuable insights and trends for businesses...
In this blog, we will compare Snowflake and Druid. It is important to note that reporting data warehouses and real-time analytics databases are different domains. Choosing the right tool for your specific requirements...
Learn how to achieve sub-second responses with Apache Druid
Learn how to achieve sub-second responses with Apache Druid. This article is an in-depth look at how Druid resolves queries and describes data modeling techniques that improve performance.
Apache Druid uses load rules to manage the ageing of segments from one historical tier to another and finally to purge old segments from the cluster. In this article, we’ll show what happens when you make...
Real-Time Analytics: Building Blocks and Architecture
This blog identifies the key technical considerations for real-time analytics. It answers what is the right data architecture and why. It spotlights the technologies used at Confluent, Reddit, Target and 1000s...
What’s new in Imply Polaris – Our Real-Time Analytics DBaaS
This blog explains some of the new features, functionality and connectivity added to Imply Polaris over the last two months. We've expanded ingestion capabilities, simplified operations and increased reliability...
Wow, that was easy – Up and running with Apache Druid
The objective of this blog is to provide a step-by-step guide on setting up Druid locally, including the use of SQL ingestion for importing data and executing analytical queries.
Tales at Scale Podcast Kicks off with the Apache Druid Origin Story
Tales at Scale cracks open the world of analytics projects and shares stories from developers and engineers who are building analytics applications or working within the real-time data space. One of the key...
Real-time Analytics Database uses partitioning and pruning to achieve its legendary performance
Apache Druid uses partitioning (splitting data) and pruning (selecting subset of data) to achieve its legendary performance. Learn how to use the CLUSTERED BY clause during ingestion for performance and high...
Easily embed analytics into your own apps with Imply’s DBaaS
This blog explains how developers can leverage Imply Polaris to embed robust visualization options directly into their own applications without them having to build a UI. This is super important because consuming...
Building an Event Analytics Pipeline with Confluent Cloud and Imply’s real time DBaaS, Polaris
Learn how to set up a pipeline that generates a simulated clickstream event stream and sends it to Confluent Cloud, processes the raw clickstream data using managed ksqlDB in Confluent Cloud, delivers the processed...
We are excited to announce the availability of Imply Polaris in Europe, specifically in AWS eu-central-1 region based in Frankfurt. Since its launch in March 2022, Imply Polaris, the fully managed Database-as-a-Service...
Should You Build or Buy Security Analytics for SecOps?
When should you build—or buy—a security analytics platform for your environment? Here are some common considerations—and how Apache Druid is the ideal foundation for any in-house security solution.
Combating financial fraud and money laundering at scale with Apache Druid
Learn how Apache Druid enables financial services firms and FinTech companies to get immediate insights from petabytes-plus data volumes for anti-fraud and anti-money laundering compliance.
This is a what's new to Imply in Dec 2022. We’ve added two new features to Imply Polaris to make it easier for your end users to take advantage of real-time insights.
Imply Pivot delivers the final mile for modern analytics applications
This blog is focused on how Imply Pivot delivers the final mile for building an anlaytics app. It showcases two customer examples - Twitch and ironsource.
For decades, analytics has been defined by the standard reporting and BI workflow, supported by the data warehouse. Now, 1000s of companies are realizing an expansion of analytics beyond reporting, which requires...
Apache Druid is at the heart of Imply. We’re an open source business, and that’s why we’re committed to making Druid the best open source database for modern analytics applications
When it comes to modern data analytics applications, speed is of the utmost importance. In this blog we discuss two approximation algorithms which can be used to greatly enhance speed with only a slight reduction...
The next chapter for Imply Polaris: celebrating 250+ accounts, continued innovation
Today we announced the next iteration of Imply Polaris, the fully managed Database-as-a-Service that helps you build modern analytics applications faster, cheaper, and with less effort. Since its launch in...
We obviously talk a lot about #ApacheDruid on here. But what are folks actually building with Druid? What is a modern analytics application, exactly? Let's find out
Elasticity is important, but beware the database that can only save you money when your application is not in use. The best solution will have excellent price-performance under all conditions.
Druid 0.23 – Features And Capabilities For Advanced Scenarios
Many of Druid’s improvements focus on building a solid foundation, including making the system more stable, easier to use, faster to scale, and better integrated with the rest of the data ecosystem. But for...
Apache Druid 0.23.0 contains over 450 updates, including new features, major performance enhancements, bug fixes, and major documentation improvements.
Imply Polaris is a fully managed database-as-a-service for building realtime analytics applications. John is the tech lead for the Polaris UI, known internally as the Unified App. It began with a profound question:...
There is a new category within data analytics emerging which is not centered in the world of reports and dashboards (the purview of data analysts and data scientists), but instead centered in the world of applications...
We are in the early stages of a stream revolution, as developers build modern transactional and analytic applications that use real-time data continuously delivered.
Developers and architects must look beyond query performance to understand the operational realities of growing and managing a high performance database and if it will consume their valuable time.
Building high performance logging analytics with Polaris and Logstash
When you think of querying with Apache Druid, you probably imagine queries over massive data sets that run in less than a second. This blog is about some of the things we did as a team to discover the user...
Horizontal scaling is the key to performance at scale, which is why every database claims this. You should investigate, though, to see how much effort it takes, especially compared to Apache Druid.
When you think of querying with Apache Druid, you probably imagine queries over massive data sets that run in less than a second. This blog is about some of the things we did as a team to discover the user...
Building Analytics for External Users is a Whole Different Animal
Analytics aren’t just for internal stakeholders anymore. If you’re building an analytics application for customers, then you’re probably wondering…what’s the right database backend?
After over 30 years of working with data analytics, we’ve been witness (and sometimes participant) to three major shifts in how we find insights from data - and now we’re looking at the fourth.
Every year industry pundits predict data and analytics becoming more valuable the following year. But this doesn’t take a crystal ball to predict. There’s instead something much more interesting happening...
Today, I'm prepared to share our progress on this effort and some of our plans for the future. But before diving further into that, let's take a closer look at how Druid's core query engine executes queries,...
Product Update: SSO, Cluster level authorization, OAuth 2.0 and more security features
When you think of querying with Apache Druid, you probably imagine queries over massive data sets that run in less than a second. This blog is about some of the things we did as a team to discover the user...
When you think of querying with Apache Druid, you probably imagine queries over massive data sets that run in less than a second. This blog is about some of the things we did as a team to discover the user...
Druid Nails Cost Efficiency Challenge Against ClickHouse & Rockset
To make a long story short, we were pleased to confirm that Druid is 2 times faster than ClickHouse and 8 times faster than Rockset with fewer hardware resources!.
Unveiling Project Shapeshift Nov. 9th at Druid Summit 2021
There is a new category within data analytics emerging which is not centered in the world of reports and dashboards (the purview of data analysts and data scientists), but instead centered in the world of applications...
How we made long-running queries work in Apache Druid
When you think of querying with Apache Druid, you probably imagine queries over massive data sets that run in less than a second. This blog is about some of the things we did as a team to discover the user...
Uneven traffic flow in streaming pipelines is a common problem. Providing the right level of resources to keep up with spikes in demand is a requirement in order to deliver timely analytics.
Community Discoveries: multi-value dimensions in Apache Druid
Hellmar Becker is an Imply solutions engineer based in Germany, where he has been delving into the nooks-and-crannies of multi-valued dimension support in Druid. In this interview, Hellmar explains why...
Community Spotlight: Apache Pulsar and Apache Druid get close…
The community team at Imply spoke with an Apache Pulsar community member, Giannis Polyzos, about how collaboration between open source communities generates great things, and more specifically, about how...
Meet the team: Abhishek Agarwal, engineering lead in India
Abhishek is Imply’s first engineer in India. We spoke to him about setting up our operations in Bangalore and asked what kind of local talent the company is looking for.
Jihoon Son is a software engineer at Imply who works on Apache Druid®. He explains what drew him to Imply five years ago and why he’s even more inspired by the company today.