A Little History
The Bowtie team has been working on Houndstooth over the past year and we are proud to finally introduce our editor to the open source community! To better understand the component structure and architectural decisions, it might be helpful to start off with some background of the development and evolution of the app.
Since the ideation stages of Houndstooth, our team has been experimenting with different approaches of how to best engineer the app. Our primary goal was to structure the frontend in a way that would be scalable while maintaining the app’s integrity through consistent and reliable patterns of component hierarchy and state management. State management was a concern, with features like staging commits and toggling back and forth between the File Editor and Collection Editor. Both the File Editor and Collection Editor are very different environments that interact with the state differently, so we felt it would be useful to have a global state management system to allow all components to interact with a repo, collection items, and staged files directly.
For the first version of Houndstooth, we decided to solve this problem by managing state using Redux, and designing most of our React components as Class based components. As we got further into the development, the complexities grew. Our components were lengthy, complicated, and difficult to understand. This sparked conversations of exploring different and better ways to approach the structural design of the app without Redux.
Enter Recompose and the Atomic Methodology.
The combination of the Recompose library and the Atomic Methodology for component hierarchy solved a lot of problems Houndstooth was facing. This pattern also made the app more performant, lightweight, and organized.
The Atomic Methodology organizes components into 4 categories:
- Atoms: Very basic, stateless components that act as building blocks for the app. These could include basic HTML elements like form labels, inputs, buttons, etc.
- Molecules: Built on a combination of Atoms, but they are still fairly basic and stateless. Molecules rely only on general props in order to maintain its reusability. The components are expected to be fully functional in any parent component for any application without modifications.
- Organisms: More robust components built of Atoms and Molecules. Organisms can have state that is specific to the application, but typically just internal state that the rest of the app does not rely on.
- Ecosystems: Ecosystems are grouped by the data they manage. You can think of an Ecosystem as the controller for a specific group of data. They control the flow of shared data to their children and routing to nested components. The views of Ecosystems should not be too complex. Essentially just a rendering of organisms and routes.
- Environments: Environments are similar to Ecosystems, but they serve as a wrapper for the entire application. It is what actually gets rendered by React. They manage very high level data that everything in the app needs to know, i.e. authenticated user.
With this methodology, we are able to group our data and organize our components accordingly. This also allows us to fully capitalize on the reusability of our components! The image below shows how Houndstooth ecosystems are organized:
At this point you may be asking ‘where does Recompose come in’? Recompose is a utility library for React components intended to allow a user to build components based on composition, rather than class inheritance. Recompose methods create a chain of higher order components to inject logic, props, state, and handlers into a block of renderable jsx. With the atomic organization of components this becomes a very powerful combination. We are able to create only stateless, presentational components that are more performant, lightweight, testable, and abstracted, then use containers to completely separate out the logic.
Our containers are separated into component specific containers and helper containers. You will find component specific containers in the same directory as the view file and helper containers inside of `/src/helpers`. The helper containers are grouped by what they manage.
For example, we have a withFormatting container that contains all our formatting helper functions. If a component needs to format a currency for example, we can inject the withFormatting into the component’s container. Now, that component has access to all the format helper functions. We don’t need to worry about passing these props and handlers down to their nested components, we just inject it into their containers.
We are very proud of how far Houndstooth has come and we hope the open source community will find this helpful.
If you wish to contribute to the Houndstooth open source project, and we hope you will, please take the time to learn some of the patterns we are using to maintain consistency.
You can learn more about Houndstooth and start using it at https://houndstooth.work
To contribute, visit the repo on GitHub at https://github.com/bowtie-co/houndstooth