Turn yourself into a senior ReactJS developer with these simple upgrades.
Take it from me, if you want to be a senior developer, one ingredient that would affect almost everything about you is how you write code that can scale.
Listen up, aspiring senior developers! If you're aiming for that coveted status, one crucial factor will influence almost every aspect of your journey—writing scalable code. It's not just a piece of advice; it's the foundation for various other principles. Think design patterns, folder structures, and more. They all revolve around one central goal: crafting code that scales.
Sure, many developers create functional code and call it a day, but here's the catch: if you've got a seasoned perspective, you can identify code that's bound to crumble or turn into a maintenance nightmare as your project expands. My mentor, Ben Guma, always emphasizes one thing: "Elegant code." It's the secret sauce. Elegant code sets you apart, makes your apps resilient, and turns your codebase into a scalable masterpiece.
Let me share a handful of strategies or tips that you can put into action today, and trust me, you'll thank yourself for it.
Single Responsibility Principle
When building React applications, one of the first things to ensure is that each component has one and only one simple responsibility. The name of the component should be descriptive of that responsibility. If you notice the component performing a different responsibility, that is an indication that it needs to be further broken down. Except for very complex UI logic, 50%-60% of the components in your React application should be implementable within 100 lines of code including imports. And 85% should be implementable within 200 lines. This is a very fair standard to live by. In fact, I have a significant number of my components under 70 lines.
But then, how do I achieve that? You may ask.
The truth is, there has to be a mental model you would use to approach the creation and use of components for you to do it right and in a manner that is consistent and predictable. Remember the goal is not about making the code smaller. The real goal is to make the code more scalable and maintainable and making the code smaller is a means to that end. You have to have a framework for doing that so that there can be consistency and ease of collaboration if possible. It's more like a high-level language that you can use to think about your application and even communicate with other developers
Page components
Page components, true to their name, serve the specific purpose of rendering all the other components that come together to constitute a page within a software application. The primary function of these components is to orchestrate the layout and presentation of the various UI elements on a given page.
Although the concept appears simple at first glance, there are scenarios where page components may involve additional tasks, such as logic implementation, data fetching, or other operations crucial to rendering the contained components effectively. Despite these potential complexities, embracing this conceptual framework from the initial stages of development facilitates the optimization of page components, ensuring alignment with the overarching principle of their singular responsibility.
Rendering Role: Page components take charge of showcasing and coordinating the appearance of all elements that come together to shape a page. They serve as the conductors orchestrating the layout of the entire page.
Singular Responsibility: The fundamental principle guiding page components is their exclusive responsibility for showcasing other components. This specific role contributes to sustaining a straightforward and easily comprehensible structure in the application.
Optimization Potential: By initiating the practice of incorporating dedicated page components right from the start, there's room for fine-tuning. You can streamline the rendering process, ensuring it's more efficient and aligning with established best practices.
Beyond Rendering: While the primary duty involves rendering, page components might also handle extra tasks like fetching data, implementing specific logic, or managing other operations vital for the seamless functioning of the enclosed components.
Framework Perspective: Visualizing pages as compositions of distinct, well-defined components sets up a conceptual framework steering the development journey. This framework supports the maintenance of a well-organized and scalable codebase.
Consider an example: let's say you are tasked with implementing a UI for a "forgot password" feature. The page consists of a logo, a card containing input fields, a countdown timer, and a button. While it might seem tempting for a junior developer to start implementing all the code directly within the component responsible for rendering the page, this approach is suboptimal. Let me illustrate with an example.
So I have this page here called forgot-password.tsx. It renders the components that make up the page and that is all there is all to it.
If observe the code above, you'll see that it imports components and renders them to the screen. No logic is involved. We have several components there and each plays a unique role. This brings me to the second kind of components
UI components:
UI components, or User Interface components, are fundamental building blocks in the design and development of software applications. They represent individual, reusable elements that collectively form the visual interface users interact with. Each UI component is designed to manage and represent a specific part of the user interface, contributing to the overall look, feel, and functionality of the application.
UI components, or User Interface components, are fundamental building blocks in the design and development of software applications. They represent individual, reusable elements that collectively form the visual interface users interact with. Each UI component is designed to manage and represent a specific part of the user interface, contributing to the overall look, feel, and functionality of the application.
Key Characteristics of UI Components:
Modularity: UI components are modular, meaning they are self-contained and can be easily reused across different parts of the application. This modularity promotes consistency in design and behaviour.
Reusability: Components are designed for reuse. Once created, a UI component can be used multiple times within the same application or across different projects, saving development time and effort.
Isolation: Each UI component operates independently, encapsulating its internal logic and design. This isolation reduces dependencies and makes it easier to manage and maintain the codebase.
Customizability: UI components often come with customizable properties or parameters, allowing developers to adapt them to specific needs without altering the component's core functionality.
Consistency: By using the same UI components throughout the application, a consistent and cohesive user experience is achieved. This consistency is crucial for creating an intuitive and user-friendly interface.
Interactivity: UI components can include interactive elements, such as buttons, input fields, or sliders, allowing users to engage with the application. These interactive features enhance the overall user experience.
Ease of Testing: Since UI components represent specific visual elements, testing them becomes more straightforward. Developers can focus on testing individual components to ensure they function correctly.
In the given example, there's a component named FormCard, and its structure is illustrated below:
At first glance, this might not seem like a significant abstraction. However, the motivation behind abstracting it lies in the concept of reusability. In many applications, dealing with an average of 30 forms is not uncommon. Styling each form individually would be cumbersome and challenging to maintain.
For those familiar with Tailwind CSS, one might question why not create a custom utility class for styling since it's primarily about abstracting styling. Indeed, using a custom utility class is a valid approach. However, the decision to abstract FormCard as a component goes beyond styling. It opens up the possibility of incorporating other components that are generic to the form card, such as a title component, a back button (if needed), a cancel button, and various other common UI elements. This approach offers a more modular and reusable structure for form-related components in the application.
Feature Components
Feature components refer to modular units of code that are specifically designed to implement a single feature within a software application. These components are encapsulated units of functionality that focus on providing a specific capability or service to enhance the overall functionality of the application.
Key Characteristics of Feature Component
Modularity: Each feature component is modular, meaning it is designed as an independent and self-contained unit. This modularity promotes code organization and reusability.
Single Responsibility: Feature components adhere to the principle of a single responsibility, concentrating on implementing a specific feature or functionality without unnecessary complexity.
Encapsulation: The internal workings of a feature component are encapsulated, meaning that the implementation details are hidden from the rest of the application. This helps in maintaining a clean and well-organized codebase.
Reusability: Feature components can be reused across different parts of the application or even in other projects. This reusability is advantageous for maintaining consistency and reducing redundant code.
Scalability: As new features are added to an application, feature components can be easily integrated or extended, contributing to the scalability of the overall system.
Testing: Feature components are conducive to effective testing. Since they represent specific features, testing becomes more focused and manageable.
Here's an example of a feature component. It implements a simple feature of submitting an email for a forgot password.
In the realm of software development, particularly within large-scale applications, it's crucial to recognize that numerous significant features often surpass the capacity of a single component. The recommended approach involves a comprehensive understanding of the inner workings of a given feature, delving into the UI interactions, logic, and behaviours that constitute the foundational elements of this feature. This deep understanding becomes the groundwork for breaking down the feature into more manageable sub-features, allowing for abstraction into dedicated components, helper functions, and hooks where necessary. This systematic breakdown enhances code modularity and maintainability, promoting a more scalable and efficient development process
Compound components
On several occasions, you will encounter situations where you would want to render multiple components that must be used together. Compound components are created by composing multiple components to work together seamlessly as a single, unified unit. Unlike a single monolithic component, compound components are designed to be used together to achieve a specific functionality or feature.
Here's a step-by-step explanation of how compound components are typically created in React:
Identify the Feature or Functionality:
Begin by identifying a specific feature or functionality that can be broken down into smaller, reusable parts.
Create Individual Components:
Break down the identified feature into individual components, each responsible for a specific aspect of the functionality. These components are often referred to as "compound components."
Define a Parent Component:
Create a parent component that will serve as the container for the compound components. This parent component is responsible for managing the state and behavior that the compound components share.
Pass Data and Functions as Props:
Pass data and functions down to the compound components as props. This allows the parent component to control the state and behaviour of the entire compound component system.
Encourage Composition:
Encourage users of the compound component to compose their UI by using the individual components within the parent component. This can be achieved by exposing the individual components as properties of the parent component.
Maintain Separation of Concerns:
Ensure that each compound component maintains a clear separation of concerns. Individual components should focus on specific tasks and not be overly dependent on the internal workings of other components.
Provide Defaults and Flexibility:
Allow users of the compound components to customize their behaviour by providing default values and flexibility through props. This ensures that the compound components can be used in various scenarios. Allow for props to be passed to the component to enable styling, handling of events and so on.
Let's look at an example of a custom select component.
Let's break down the code above.
Select
Component:The
Select
component is a wrapper for the native HTMLselect
element. It utilizes theuseState
hook to manage theactiveOption
state, representing the currently selected option.The
SelectContext.Provider
is used to provide the context value (activeOption and setActiveOption) to its descendants. It renders the nativeselect
element and passes down its children (options).
useSelectContext
Hook:A custom hook,
useSelectContext
, is defined to consume theSelectContext
within the child components. It uses theuseContext
hook to access the context and throws an error if used outside the context of aSelect
component.Option
Component:The
Option
component represents an individual option within theSelect
component. It uses theuseSelectContext
hook to access the context and retrieveactiveOption
andsetActiveOption
.The component dynamically applies styles based on whether the option is active or not. It renders a native
option
element and handles the click event to set the active option.
Compound Component Formation:
- The
Select.Option
assignment extends theSelect
component with anOption
property, allowing the usage ofSelect.Option
to define options.
- The
This code therefore provides a flexible Select
component that can be used to create custom-styled dropdowns in a React application. The use of the Context API allows child components (options) to easily access and update the state of the Select
component.
Extracting complex/reusable logic into hooks
To maintain the elegance of your code, you can't do without this. You just can't. You can't be writing multiple useEffects all over your components. You can't be running the data fetching and state update logic directly in your components. That's junior! Refactoring complex logic into custom hooks is a fundamental practice for maintaining clean and scalable code in React applications. You have to level up. This approach aligns with the principle of code separation, promoting a modular structure and enhancing code readability. This not only improves the overall structure of the codebase but also facilitates code reuse and makes it easier to reason about each piece of functionality. It reflects a more sophisticated understanding of code organization and promotes best practices in React development.
Here's a very simple but highly reused hook in almost all my codebases. I use it for toggling things like modals, popups, drop-downs, name it. The logic it abstracts is so simple but this is to show you how far you can take it with hooks, and honestly, that's how far you should take it.
This is another hook I use for implementing countdowns for One Time Passwords. I use it about a couple of times or more in an average project. It's way better to destructure 3 variables from a hook than to be writing all this logic within your component.
In conclusion, the journey to becoming a senior developer involves mastering the art of writing scalable code, a foundational principle that influences various aspects of development. This goes beyond mere functionality; it's about crafting elegant code that stands the test of scalability, making applications resilient and codebases easy to maintain.
Is this all there is to levelling up to becoming a senior dev? Of course not, but this is a fundamental step you have to take, and for some, this is the very missing piece. I'll be back with more for your to enjoy.
If you liked this content, pls drop a like, share what you love about the article and follow me on my social media pages.
LinkedIn: https://www.linkedin.com/in/prophet-ogwuche/
Twitter: https://twitter.com/dProphetBestman