The Select component is analogous to the <select> HTML tag. It accepts props such as a list of available options and the currently selected option, but it does not own any state. Not even a state to indicate whether or not it is currently expanded. Select is composed of DropdownToggle which handles dropdown expanded/collapsed state.
This component takes in a trigger element and children that will be displayed in a dropdown HoverCard when the trigger is clicked. Select passes a button with a downward facing arrow icon as the trigger into DropdownToggle. It also passes a selectable list of options as the children into DropdownToggle.
TooltipToggle is similar in scope to DropdownToggle because it accepts a trigger component and manages state to determine whether to show its children in a HoverCard. The difference is in how it decides to show the HoverCard; the interaction logic is different. While the DropdownToggle listens for clicks on the trigger element, the TooltipToggle listens for mouse hover events. It also doesnt close when the ESC key is pressed, but the DropdownToggle does.
HoverCard is the star of the show! It powers the UI markup, styles, and some of the relevant event handlers for both tooltips and dropdowns. It contains no state and it doesnt know if its open or closed. If it exists, its open. You close it by unmounting it.
It accepts an anchor element as a prop, which is the element that the floating HoverCard positions itself around. HoverCard also has multiple looks and feels, a.k.a. flavors. One flavor is called "tooltip" which has a black background and white text color. Another is called "dropdown", which is used by the the Select component, and it has a white background and a box-shadow.
HoverCard also takes in a myriad of props for customization, such as whether or not to show a triangular caret (TooltipToggle enables this prop), or what the position of the HoverCard is relative to the anchor (TooltipToggle uses top while DropdownToggle uses bottom), and so on. HoverCard also listens for certain events (such as clicks) that happen outside of the HoverCard or key presses to the ESC key. When these events happen, HoverCard notifies the parent component via prop callbacks so that the parent can decide if it wants to close the HoverCard. One other responsibility of HoverCard is to detect whether its overflowing outside of the window and - if so - fix its positioning . (This functionality can be turned off via a prop).
Extracting all of the UI implementation code into HoverCard allows the higher level components like DropdownToggle and TooltipToggle to only focus on state management and interaction logic instead of re-implementing the nitty gritty, DOM positioning and styling code that is shared between all hover-y things in the UI.
This is just one example of separating UI details from interaction logic. Following this principle for all components and carefully evaluating where new pieces of state should live has really increased our ability to reuse code.
Flux is great for storing application state that either doesnt logically belong to any one specific component or state that should persist after an unmount. A common bit of advice is to never use this.state and to put everything in Flux stores -- however, this isnt quite right. You should feel free to use this.state for component specific state that isnt relevant after something unmounts. An example of such state is the isCurrentlyOpen state of DropdownToggle.
Flux is also quite verbose, which makes it inconvenient for data state, state that is persisted to the server. We currently use a global Backbone model cache for data fetching and saving but were also experimenting with a Relay-like system for REST apis. (Stay tuned for more on this topic).
For all other state, we have been able to gradually introduce Flux into our code base. Its great because it doesnt require a rewrite; you can use it where you want. Its easy to unit test, easy to scale, and has provided some cool benefits like resolving circular dependencies in our core modules. It has also removed the need for hacky singleton components.
The final scaling tip that we want to share in this post is this: ensure that React components are your primary unit of reuse. Every one of our React components has an associated CSS file. Some of our components dont even have any JS interactions or functionality. Theyre just bundles of markup and styles.
Weve been staying away from Bootstrap-like global styles through class names. You can definitely still use Bootstrap, but wrapping the Bootstrap components in your own React components will save you time in the long run. For example, having an Icon React component that encapsulates that markup and accepts the icon name as a prop is better than having to remember exactly what markup and class names to use for icons, which in turn makes refactoring easier. It also makes it easier to add functionality to these components later on.
Although we do define a few global styles for elements such as anchors and headings, as well as many global SCSS variables, we dont really define global css classes. Being careful to reuse UI mostly through React components has made us more productive as a team because the code is more consistent and predictable.
And thats about it! These have been some of the guiding principles that helped us build a robust React architecture that has scaled with the size of our engineering team and the complexity of the application. Please feel free to comment with your thoughts and experiences with the ideas in this post.
Check out React Tips and Best Practices if you're looking for even more!