The Code Example
By the end of this article, I will show you how to build a simple search application and you can view the full code on my CodePen example page.
Type of Components
Whether you are building components that will be used among many applications or for a single one, there is always the same type of components you can use. It is very important to respect their types and to think about your components beforehand to decide how to split and build them.
You have a few component types to use.
- Display Component
- View Component
- Higher-Order Component
- Page Component
- Global Component
On top of that, you have different services in your application responsible for retrieving, storing, transforming, and provide data. You also have utilities that help with tiny details of your application. As you can see, your web application can get very complex and it is much better to separate your View and View Control from the Model logic as much as possible.
A display component is one that does just that. It displays what it is provided or based on what it is provided. It wastes no time in including complex logic, and for the small logic, it may have, it all has to do with how to display what it was given. It should be kept simple and small, preferably a single thing (an atom). Display components come in 3 main flavors:
A dumb component is a display component that does not contain any state management. It may set something that helps it in the rendering but its only focus is rendering.
The following example is a generic Input component that can be used as a foundation to build more complex and styled-components. The nice thing it does is handle the change event by persisting the event which comes in handy for the React application.
A display component can contain simple state management related to rendering or user interaction. It is very important to keep it simple. These types of components normally expand on stateless components in order to handle some user interaction logic.
For the following example, I have created a Search Field from the previous generic InputField component. This one ensures empty fields are ignored which helps avoid rendering for empty(blank) inputs. Notice it also decreases the number of props needed for a field. It is much specific.
One thing you will notice is that at each level the component becomes simpler by abstracting logic and props needed.
A utility component is a much special display component. You can use it to remove any logic from simpler components. For example, we could create a utility component that handles the field input for the above example so we could use it for other Input Field derived components.
It does not render anything or anything specific. It simply takes information and may do things like format, clean up, simplify, etc. It may take components or functions that render components and it helps the component do the rendering.
For the following example, a much simpler version of flatlist-react component, the List component takes some dictionary or list-like data and ensures to format it to change it into an array for rendering which allows us to pass any data to be treated as a list. It takes 2 render functions for when there is or there isn’t any item in the list. Another special thing it does is it may take an HTML tag string to wrap the list in.
A view component is a little more elaborated but still should be kept simple and small. View components are a composition of Display components, a container. It combines many smaller components and handles the communication between them. It should not take care of fetching and managing data. They are often stateful and when they are, all states are related to components communication.
In the following example, I composed a simple search view with the display components you saw before. There are hints of data that need to be fetched but it keeps that as something to be provided. Common mistake developers do is include data management(redux, flux) and fetching(service, API) in these components which often complicates them. Another detail is that it is not a stateful view. All smaller details and interactions are already handled in the smaller components.
Another small detail in the above example is the element component ListItem. Perhaps the smallest component you can create and in the React world, it is referred to as Element.
Template components are more generic view components. Their sole purpose is to render components in a specific fashion, not just about layout and style though. A famous example of a logic template component can be a dialog which can be used to render anything in the container but it always works the same way. You can create a dialog template that you can use to build any specific dialog upon.
Higher-order components are Functions that take a component and returns a new component, perfect for stripping view components from any logic related to fetching and managing data. A common mistake here would overload it with a lot of data handling. Ideal, you should have many higher-order components and make them specific to something. Put all your redux, flux, services, context, and data related logic in them.
In the following example, I have a simple Higher-Order component withSearchData which takes a component and then returns a functional component that renders the provided component with any props. Inside, it includes the logic for calling the API whenever a search term is provided and then passes the results down to the component.
Right after, before usage, I created a new component that combines the view component(SearchResultsView) and the HOC(withSearchData) creating a new component I can then use anywhere I want.
As you can see below, we abstracted almost everything into a final component we can just place anywhere, much like a widget. With the exception of the HOC for data handling, all components are not specific to any application and can easily be reused in another application. The only thing we change is the HOC to handle the data.
A page component can be a view component or a collection of many view components in a template that is rendered under a URL path. To put it simply, page components are view components with a URL location.
Global components are components that do not need to be included inside any component. They are available globally which means they are included in the App once and are normally tight to some service or global state to control when they show to the user. These components can be triggered into view by any component and at any path.
An example of such a component is the Notification or SnackBar component which you trigger by calling a method in a service or by changing a global state.
Whenever you are building components try to go from a single component to composition by breaking your components down into smaller pieces then use them to compose bigger components.
Whenever possible, you should also try to go from generic to a more specific component, just like the InputField example above. Create a base component that you can use to create a more specific variant of it either for styling it differently or include specific features or parts. This will allow you to abstract props making it easier and easier to use as it becomes more specific.
Optimization and Test of Components
From the examples I provided, notice that the props are simple and easier to memoize which makes it easier to optimize these components for a minimum possible re-render. The components are simple and easy to wrap inside React.memo or use with useMemo for even better performance.
Smaller and simpler components are easier to optimize, test, and style. You should always approach your components from a Lego perspective. Create small parts, compose the small parts for the bigger pieces, and attach data to have an independent block that you can use to put together your app.
Planning your app UI is crucial to make it work and be maintained easily. I’d say, start with the design or a sketch of what you want. Start creating the smallest components first by breaking the UI into its smallest parts. Then start composing abstracting and splitting logic into utility for feasibility. Any component, small or big, that is meant to be reused should not contain anything that ties it to the App. Move as much logic as possible to HOC and utilities that may or not be specific to the app itself.
It does take practice and time to grow accustomed to creating small parts and composing, but it goes a long way as it makes the app easier to test, maintain, and reuse. It should be a strategy to start with from the beginning. Having to refactor a big mess later is not something you want to do.
I am on my YouTube Channel for more examples and projects to share with you. Let’s stay connect and best of luck to you!