# Monolithic Components, Composable Components ## Introduction Building reusable UI components is a non trivial task, as we need to anticipate a number of things when planing for reuseability. On the one end of the spectrum we want to enable customization and on the other side we want to avoid developers doing the wrong thing, like breaking the component or displaying invalid states. To get a better understanding of what we need to think about and consider upfront, we will build a non-trivial UI component, that displays tags. Our `Tags` component will take care of managing and displaying tags. The following examples are all built with [**Tachyons**](https://tachyons.io) and [**React**](https://reactjs.org/), but these ideas apply to any UI component and any general styling approach. ## Basics Let's talk about `Tags` first. Tags should enable to add, display and delete tags. It should also enable to style the component as needed as well as leave some room for configuring the behaviour and representation of these tags. Our first naive approach might be to define a `` component, that expects an array of tags and displays these tags. Optionally there should be a capability to add new tags and the possibility to delete a tag. The very initial API implementation considers all these cases. ```js type TagsProps = { items: Array, onAdd?: (tag: string) => void, onRemove?: (tag: string) => void }; ``` ![Tags Component: Basic](https://user-images.githubusercontent.com/718727/50054057-2ca32400-013e-11e9-9a2f-f1b2c3fec1bd.png) So, we can already see that it renders a provided set of tags and displays an input element for adding new tags. This implementation also has some assumptions about these optional types. If no `onAdd` function is provided, we don't display an input element either, same for removing tags. How can we style our tag representations? One approach is to expose another prop for enabling to define the theme. We might offer two or three different options, like `light`, `default` and `dark`. ```js type Theme = "light" | "default" | "dark"; type TagsProps = { items: Array, onAdd?: (tag: string) => void, onRemove?: (tag: string) => void, theme?: Theme }; ``` Developers using this component can now switch between different modes, f.e. using the following declaration would return a dark themed tags component. ```js ``` ![Tags Component: Dark Mode](https://user-images.githubusercontent.com/718727/50054058-2f9e1480-013e-11e9-9e77-f3b177b57ec3.png) Up until now we were able to design our API to handle all expected basic use cases. But let's think about how a developer might want to use this `Tag` component for a minute. How could we display the input box below the tags for example? There is no way to do this with the `Tags` component at the moment. ## Refactoring Let's take a step back for a minute and think about how we might enable developers to freely define where the input box should be positioned. One quick way is to add another prop, which could define some sort of ordering in form of an array f.e. `ordering={['tags', 'input']}`. But this looks very improvised and leaves room for errors. We have a better way to solve this problem. We can leverage composition by exposing the underlying building blocks to user land. `Tags` uses `InputBox` and `Tag` under the hood, we can export these components and make them available. ![Tags Component: Ordering](https://user-images.githubusercontent.com/718727/50054060-32006e80-013e-11e9-8f5a-b72b9f062394.png) Let's take a closer look at how the components are structured. ```js
{this.state.items.map(item => ( ))}
``` Interestingly we don't use the `Tags` component anymore, we're mapping over the tags explicitly, but we can use the `TagInput` directly, as it handles local state independently. Although this approach gives developers control on how to layout the tags, it also means added work that we wanted to avoid in the first place. How can we avoid having to map over these items and still enable to define the ordering? We need a better solution. Let's define a `TagItems` component again. ```js type TagItemsProps = { items: Array, onRemove?: (tag: string) => void, theme?: Theme }; ; ``` We can decouple our `TagItems` component from the `TagsInput` component. It's up to the developer to use the input component, but also enables to define the ordering and layout as needed. ```js
``` This is looking quite sensible already. We can explicitly define the layout and ordering of the components, without having to handle any internals manually. Now if we think about further requirements, we can anticipate the need to define some specific styles for a rendered tag or the input box. We have exposed the main building blocks, but how can we adapt the theming to suit an existing design? Our tag components need to address the possibility to override specific styling aspects when needed. One possible way is to add classes or inline-styles. The better question that needs answering is if our main building blocks should even be concerned with any view information. One possible approach is to define a callback for defining what low level building block we want to actually use. Maybe some developer would like to add a different close icon? Before we continue, let's think about some facts regarding our components. Our `TagInput` component takes care of managing local state and enabling to access the tag value when a user presses enter. The `Tags` component iterates over the provided tags and renders them, passing remove capabilities to every `Tag` component. With these building blocks available we can already ensure that any developer can display decent looking tags. But there are limits we can already see, when some specific requirements arise in the future. Currently we have coupled state and view handling. Our next step is decouple the actual `Input` component, that takes care of any view concerns, from the `TagsInput` component, that manages state handling. Now that we have a better understanding, let's see what further decoupling our components will bring us. ```js type InputProps = { value: string }; const Input = ({ value, ...additionalProps }: InputProps) => { return ( ); }; ``` The above code is the smallest building block we might want to offer. It opens up the possibility to override specific stylings or even the `className` attribute if needed. We also don't define how the onChange or onSubmit is handled in this case. Our `TagsInput` passes an onChange and onKeypress prop, but maybe we want to submit via a button in a specific case. Our `TagsInput` doesn't care about the actual styling and is only concerned with managing state and supplying functionalities for updating that state as well as submitting that state. For this example we will provide render prop, but other appeoaches like higher order components or other approaches work the same, so we can reuse the state handling logic when needed and provide our own input component if needed. The state handling in this case might not seem to be worth the effort, but we might be doing more complex things in a more advanced implementation. It should highlight the fact that we can expose state and view handling now. Developer land can freely compose and mix as needed now. Check the following example for a better understanding. ```js type StateType = { value: string }; class TagInput extends React.Component { constructor(props: TagInputProps) { super(props); this.state = { value: props.value }; } onChange = (e: any) => { this.setState({ value: e.target.value }); }; onSubmit = (e: any) => { e.persist(); if (e.key === "Enter") { this.props.onSubmit(this.state.value); this.setState({ value: "" }); } }; render() { const { value } = this.state; const { onSubmit, value: propsTag, theme, render, ...additionalProps } = this.props; const tagsInput = { value, onKeyDown: this.onSubmit, onChange: this.onChange, ...additionalProps }; return this.props.render(tagsInput); } } ``` Our `TagItems` component doesn't do very much, it only iterates over the Items and calls `Tag` component, as already stated further up. We don't need to do much here, we can also expose the `Tag` component, as the mapping can be done manually when needed. ```js type TagItemsProps = { items: Array, onRemove?: (e: string) => void, theme?: Theme }; const TagItems = ({ items, onRemove, theme }: TagItemsProps) => ( {items.map(item => ( ))} ); ``` ## Final Thoughts This walkthrough and refactoring session, enabled us to provide a monolithic `Tags` as well as `TagInput`, `Input`, `TagItems` and `Tag` components. The standard way is to use the `Tags` component, but if there is a need for some special customization, we can now use the underlying building blocks to reconstruct the behaviour as needed. With the upcoming release of hooks, we can even expose all the building blocks in a more explicit manner. We might not need the `TagInput` component anymore, we can expose a hook instead, and use this hook internally inside `Tags`. A good indicator for exposing the underlying building blocks is when we need to start adding properties like `components={['input']}` or `components={['input', 'tags']}` to indicate which components we want displayed and in which ordering. Another interesting aspect that we can observe, after breaking a monolithic into smaller blocks, is that our top level `Tags` can be used as a default implementation, a composition of the smaller building blocks. ```javascript type TagsProps = { items: Array; onRemove: (e: string) => void; onSubmit: (e: string) => void; theme?: Theme; }; const Tags = ({ items, onRemove, onSubmit, theme }: TagsProps) => (
} />
); ``` We can now start adding some tags. ![Tags Component: Final](https://user-images.githubusercontent.com/718727/50054045-ffef0c80-013d-11e9-98fe-53c5c4b23316.png) ___If you have any questions or feedback please leave a comment here or connect via Twitter: [A. Sharif](https://twitter.com/sharifsbeat)___