Understanding Design Patterns in React
Before delving into the details of React’s design patterns, we should understand what they are and why they are needed. Simply put, design patterns are repeatable solutions to commonly occurring development problems. They serve as a basic template upon which you can build up any functionality according to the given requirements while following the best practices. We can use them to save development time and reduce coding efforts since they serve as standard terminology and pre-tested solutions to known problems.
Let’s get started!
This is undoubtedly one of the most basic and widely used pattern with React components (that perhaps doesn’t need much introduction either 😅). Quite frequently the need arises to render or not render a certain JSX code based on a certain condition. This is achieved through conditional rendering. As an example, we’d want to show a button that says Log In for unauthenticated users and Log Out for signed-in users.
Typically conditional rendering is achieved using either the
&& operator or the
In some cases, we might also consider using
switch, or object literals.
React hooks have proven to be a revolutionary introduction in conjunction with functional components. They provide a simple and direct way to access common React features such as
refs, and lifecycle. We may be content using the traditional hooks but there’s more. Let’s understand the benefit of having custom hooks introduced in the mix. Think of a piece of logic that you wrote for a component, you possibly used the basic hooks like
useState. After some time, the same logic needs to be used in another new component. While copying may feel like the quickest & easiest way to do that, custom hooks to the same effect are way more fun (😉). Extracting commonly needed logic in a hook make for clean code & increases reusability & of course maintainability.
Starting off with a common use case, calling APIs in different components. Think of a component that renders a list of users after fetching the data from an API.
Since the API calls are pretty much the backbone for most components, why not extract it in 1 place. This functionality can be easily pulled in a new
useFetch hook as:
Some other possible use cases that come to mind for custom hooks could be:
● Getting window dimensions
● Accessing & setting local storage
● Toggling between boolean states, etc.
One major problem faced by React developers is Prop drilling. Prop drilling is a scenario in which data (
props) is passed down to different components until it gets to the component where the prop is needed. This easily becomes a problem when some data needs to be passed to one or more nested components deep down in the component tree since a seemingly unnecessary chain of data passing is established.
This is where the Provider pattern comes to the aid. Provider pattern allows us to store data (global or shareable in nature) in a central location. The Context Provider/Store can then pass this data to any component that needs it directly without drilling props. React’s built-in Context API is based on this approach. Some other libraries that use this pattern include
To understand this with an example, one common scenario is implementing a light/dark theme in your application. If it weren’t for the Provider pattern, our implementation would look like this:
Let’s see how introducing
Context API simplifies things.
Isn’t this better! Other possible usages for Provider pattern could be:
● Authentication state management
● Managing locale/language selection preferences, etc.
Higher Order Components Pattern
HOCs in React is an advanced technique for reusing logic in components. It is a pattern created out of React’s compositional nature. It essentially incorporates the don’t-repeat-yourself (DRY) principle of programming. Similar to higher-order functions in JS, HOCs are pure functions that take a component as an argument and return an enhanced & upgraded component. It is in line with the nature of React functional components, that’s composition over inheritance. Some real-world examples include:
As an example, consider a simple component that renders a list of users & handle various state like loading, error, and no available data.
Showing such different API fetch states is a common logic that can be easily re-used in many components. Hence to pull it out in a HOC, we can do something like:
HOCs are useful when dealing with cross-cutting concerns, specifically where we want to reuse component logic across the application. Some possible usages could be:
● Implementing logging mechanisms.
● Managing authorizations, etc.
Presentational & Container Components Pattern
As the name suggests, this approach involves dividing the components into 2 different categories & implementation strategies:
- Presentation Components: These are essentially pure stateless functional components. These are concerned with how things look. They don’t have any dependencies with any part of the application and are used to display data.
- Container Components: Unlike presentational components, Container components are more responsible for how things work. They act as a container for any side effects, stateful logic, and the presentational components themselves.
With this approach, we achieve better separation of concern (since we don’t have just 1 complex component that handles all the rendering and logical states). In addition, this provides better reusability with Presentation components (since they don’t have any dependencies, they can be easily reused for multiple scenarios).
Therefore, your aim, as a developer, should be to create stateless components even if there is no immediate scenario in which you would have to reuse that particular component. For a hierarchy of components, the best practice is to let parent components keep as much state as possible and make stateless child components.
As an example any component that renders a list could be a presentational component:
The corresponding container component for this could be:
Controlled & Uncontrolled Component Pattern
Web forms are a common requirement in a large number of applications. In React, there are two ways to handle form data in our components. The first way is by using React state within the component to handle the form data. This is called a controlled component. The second way is to let the DOM handle the form data by itself in the component. This is known as an uncontrolled component. “Uncontrolled” refers to the fact that these components are not controlled by React state rather traditional DOM mutations.
To understand these better, let’s start with the example of uncontrolled component.
Here, we use a
ref to access the input. This approach works so that you have to pull the value from the field when you need it. Let’s now see what would the controlled version of this form look like:
Here, the input’s value is always driven by the React state. This flow kind of pushes the value changes to the form component, so the Form component always has the current value of the input, without needing to ask for it explicitly. While this means you have to type a bit more code, you can now pass the value to other UI elements too, or reset it from other event handlers perhaps using props and event callbacks.
React forms have support for both controlled and uncontrolled components. We may have certain use cases where we are working with simple UI & feedback, then we might find it preferable to adopt uncontrolled components. Whilst for complex logic, it is highly recommended that we use controlled components.
Render Props Pattern
As per React's official documentation, Render Prop refers to a technique of sharing code between components using a prop whose value is function. Similar to HOCs, Render Props also serves the same purpose : dealing with cross-cutting concerns by sharing stateful logic between components.
A component implementing Render prop design pattern takes a function returning React Element as a prop and calls it instead of using its render logic. So, instead of hardcoding the logic inside each component, we can use the function prop to determine what to render.
To understand this better, let’s take an example. Suppose we have a list of products that needs to be rendered in different locations in the application. The UI experience differs for these locations, however the logic is same - fetch the products from the API & render the list.
We could easily reuse this functionality with Render Props pattern:
Some popular libraries that use the Render Props pattern include:
Compound Components Pattern
Compound components is an advanced React container pattern that provides a simple and efficient way for multiple components to share states and handle logic — working together. It provided a flexible API that enables a parent component to interact and share state with its children implicitly. Compound components are best suitable for React apps where you need to build declarative UI. This pattern is also used in some popular design libraries like
Material UI etc.
The way the traditional
options HTML elements work helps us understand this better. Both select and options work in sync to provide a dropdown form field. The select element manages and shares its state implicitly with the options elements. Consequently, although there is no explicit state declaration, the select element knows what option the user selects. Similarly here, we may use the Context API to share & manage state between the parent and child components as per need.
Diving into the code, let’s try to implement a Tab component as a compound component. Typically tabs have a list of tabs and a content section is associated with each of them. At a time, only 1 tab is active and its contents are visible. This is how we can do it:
This can now be used as:
Some other use cases where we can use this pattern include:
● Lists and list items
● Menu and menu headers, menu items, dividers.
● Table and table head, table body, table row, table cell
● Accordion with title and contents
● Switch and toggles
Layout Components Pattern
When creating a react application/website, most of the pages would be sharing the same content all over. For example the navigation bar and page footer. Instead of importing each component in every page to be rendered, it is much easier and faster to just create a layout component. Layout component help us share common sections easily across multiple pages. Just as it’s name suggests - it defines the layout of the application.
Working with reusable layouts is a very good practice, because it lets us write code once and use it in a lot of parts of your application, for eg. - we can easily reuse layouts based on Grid system or Flex Box model.
For now, let’s consider a basic example of a Layout component through which we can share the
Footer across multiple pages.