├── CITATION.cff ├── LICENSE ├── README.md └── img └── prop_drilling.png /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.0.0 2 | message: 'If you use this catalog in your work, please cite it as below.' 3 | authors: 4 | - given-names: Fabio 5 | family-names: Ferreira 6 | email: fabio.ferreira@ifsudestemg.edu.br 7 | affiliation: UFMG 8 | orcid: 'https://orcid.org/0000-0003-4485-3219' 9 | - given-names: Marco Tulio 10 | family-names: Valente 11 | email: mtvalente@gmail.com 12 | affiliation: UFMG 13 | orcid: 'https://orcid.org/0000-0002-8180-7548' 14 | title: 'Catalog of React-specific code smells' 15 | version: '1.0' 16 | date-released: '2022-04-06' 17 | url: 'https://github.com/fabiosferreira/React-Code-Smells' 18 | keywords: 19 | - react 20 | - code smells 21 | - JavaScript 22 | - Anti-Patterns, 23 | license: MIT 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Fábio Ferreira 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # React Code Smells 4 | 5 | ## Table of Contents 6 | 7 | * __[Introduction](#introduction)__ 8 | * __[Novel Smells](#novel-smells)__ 9 | * [Force Update](#force-update) 10 | * [Direct DOM Manipulation](#direct-dom-manipulation) 11 | * [Props in Initial State](#props-in-initial-state) 12 | * [Uncontrolled Components](#uncontrolled-components) 13 | * [JSX outside the render method](#jsx-outside-the-render-method) 14 | * __[Partially Novel Smells](__partially-smells)__ 15 | * [Prop Drilling](#prop-drilling) 16 | * __[Traditional Smells](traditional-smells)__ 17 | * [Inheritance instead of Composition](#inheritance-instead-of-composition) 18 | * [Duplicated Component](#duplicated-component) 19 | * [Too Many Props](#too-many-props) 20 | * [Large Component](#large-component) 21 | * [Large Files](#large-files) 22 | * [Low Cohesion](#low-cohesion) 23 | 24 | 25 | ## Introduction 26 | 27 | Released by [*Facebook*][Facebook] in 2013, [*React*][React] is a widely popular JavaScript library for building user interfaces. Due to the complexity of modern Web UIs, React applications can have hundreds of components, lines of code, and files. As a result, it is natural to expect maintainability problems in React-based UIs due to wrong or suboptimal design decisions. In this context, identifying design problems when using this library is important. For example, front-end developers can use this information for refactoring and better understanding and evolving their systems. However, few works in the scientific literature focused on studying maintainability problems in React-based UIs. 28 | 29 | Therefore, to fill this gap, we propose an initial list of code smells common in *React* applications. These smells were identified by looking for specific *React* code smells in websites, blogs, and forums (grey literature review) and interviewing six professional software developers. 30 | 31 | We did not include in our catalog smells describing low-level concerns that can be detected by linter tools[^1] (e.g., ``modify state directly``, ``array index as key``, ``no access state in setState``, ``props spreading``, etc.). Since code smells are a widely studied concept, not only in object-oriented designs but also in particular domains, we classified these smells into three major categories: [Novel Smells](#novel-smells), [Partially Novel Smells](__partially-smells), and [Traditional Smells](traditional-smells). Below, we present our catalog of smells. For each smell, we provide its definition and an illustrative example. 32 | 33 | The objective of this catalog is to help improve the quality of web applications developed using React. For this reason, we are interested in knowing React's community opinion about these code smells: *Do you agree that these code smells can be harmful? Have you seen any of them in production code? Do you have any suggestions about some React-specific code smell not cataloged by us?...* 34 | 35 | Please feel free to make pull requests and suggestions ([Issues][Issues] tab). We want to hear from you! 36 | 37 | [^1]: [React specific linting rules for ESLint][eslint-plugin-react] 38 | 39 | ## Novel Smells 40 | 41 | We claim our list includes five smells that have not been studied in the scientific literature: Force Update, Direct DOM Manipulation, Props in Initial State, Uncontrolled Components, and JSX Outside the Render Method. They refer to features very specific to React, including View updates, Components that manipulate DOM directly, Components that refer to HTML elements, Components' state, and render methods. 42 | 43 | ### Force Update 44 | JavaScript frameworks rely on a data binding mechanism to keep the View layer updated with data automatically. Particularly, React supports one-way data binding, which automatically reflects model changes in the view. For this reason, it is considered a bad practice to force the update of components or even reload the entire page to update some content, as recommended in React documentation: 45 | 46 | > Normally, you should try to avoid all uses of ``forceUpdate()`` and only read from ``this.props`` and ``this.state`` in ``render()``. 47 | 48 | For example, the following code realod the entire page to update the contents submitted through a form. 49 | 50 | ```jsx 51 | onSubmitForm = async () => { 52 | const { homeDashboardId, theme, timezone, weekStart } = this.state; 53 | await this.service.update({ homeDashboardId, theme, timezone, weekStart }); 54 | window.location.reload(); 55 | }; 56 | ``` 57 | 58 | ### Direct DOM Manipulation 59 | React uses its own representation of the DOM, called virtual DOM, to denote what to render. When props and state change, React updates the virtual DOM and send the changes to the real DOM. For this reason, manipulating DOM using vanilla JavaScript can cause inconsistencies between React’s virtual DOM and the real DOM. In the following code, the component ``Portal`` creates and manipulates DOM elements through methods such as ``document.createElement('div')``, ``document.body.appendChild(this.node)`` and ``this.node.setAttribute``. 60 | 61 | ```jsx 62 | class Portal extends React.PureComponent { 63 | node: HTMLElement; 64 | 65 | constructor(props: PortalProps) { 66 | super(props); 67 | const { index = 0, origin = 'query', style } = props; 68 | this.node = document.createElement('div'); 69 | this.node.setAttribute('style', style); 70 | this.node.classList.add(`slate-typeahead`, `slate-typeahead-${origin}-${index}`); 71 | document.body.appendChild(this.node); 72 | } 73 | 74 | componentWillUnmount() { 75 | document.body.removeChild(this.node); 76 | } 77 | 78 | render() { 79 | if (this.props.isOpen) { 80 | this.node.setAttribute('style', this.props.style); 81 | this.node.classList.add(`slate-typeahead--open`); 82 | return ReactDOM.createPortal(this.props.children, this.node); 83 | } else { 84 | this.node.classList.remove(`slate-typeahead--open`); 85 | } 86 | 87 | return null; 88 | } 89 | } 90 | ``` 91 | 92 | ### Props in Initial State 93 | Initializing state with props makes the component to ignore props updates. If the props values change, the component will render its first values. React documentation also states about this smell: 94 | 95 | > Using props to generate state in the constructor (or \texttt{getInitialState}) often leads to duplication of "source of truth", for example where the real data is. This is because the constructor (or \texttt{getInitialState}) is only invoked when the component is first created. 96 | 97 | For example, the component ``ListNames`` initializes the state of names and ages with props. 98 | 99 | ```jsx 100 | class ListNames extends Component { 101 | constructor(props){ 102 | super(props); 103 | this.state = { 104 | names: props.names, 105 | ages: props.ages, 106 | }; 107 | } 108 | } 109 | ``` 110 | 111 | ### Uncontrolled Components 112 | Forms are key elements in Web UIs. The official React documentation recommends using controlled components to implement forms. In such components, React fully handles the form’s data. On the other hand, developers can implement forms using vanilla HTML, where the form data is handled by the DOM itself, leading to so-called Uncontrolled Components. For example, the following ``AddComment`` component is considered Uncontrolled, since it uses a ``ref`` to get the comment value. 113 | 114 | ```jsx 115 | class AddComment extends React.Component { 116 | // ... 117 | render() { 118 | return ( 119 |
120 | 121 | 122 | 123 |
124 | ); 125 | } 126 | } 127 | ``` 128 | On the other hand, when developers use controlled components, all data is stored in the component’s state, making it easy to validate fields instantly or render them conditionally. 129 | 130 | ### JSX outside the render method 131 | 132 | The ``render`` method is the only method that is mandatory in a class component. It provides the JSX template for all UI elements. Normally, we assume that all JSX code is confined in the ``render`` method. The existence of JSX code in other methods indicates that the component is assuming too many responsibilities and providing a complex UI element. As a result, it is more difficult to reuse the component in other pages or apps. For example, in the following ``Gallery`` component, we have three ``render`` methods: one to render an ``Image`` (that calls ``renderComment()``), the method that only renders ``Comments``, and the main ``render`` method (that calls ``renderImage()``). However, suppose we want to handle just a ``Comment``. In this case, it is impossible to rely on ``Gallery`` since this component also provides other visual elements, such as the ones needed to represent an ``Image``. 133 | 134 | ```jsx 135 | class Gallery extends React.Component { 136 | renderComment() { 137 | return (
...
) 138 | } 139 | 140 | renderImage() { 141 | return ( 142 |
143 | ... 144 | {this.renderComment()} 145 |
) 146 | } 147 | 148 | render() { 149 | return ( 150 |
151 | {this.renderIamge()} 152 | {this.renderComment()} 153 | ... 154 |
155 | ); 156 | } 157 | } 158 | ``` 159 | 160 | In summary, the presence of JSX code in multiple methods indicates a complex UI element, which might be decomposed into smaller and reusable ones. 161 | 162 | ## Partially Novel Smells 163 | 164 | We classified a single smell in our catalog as partially novel: Prop Drilling. For example, Ousterhout et. al. mention a design red flag called Pass-Through Methods, i.e., a method that does nothing but pass its arguments to another method with a similar signature. However, in our case, Prop Drilling does not relate to methods but components. As a second observation, Prop Drilling resembles to some degree a Middle Man class, i.e., a class that delegates most of its work to other classes. However, Middle Man classes by definition are anemic in the terms of behavior and data, which is not the case of Prop Drilling. In other words, a complex component can also be used to pass through properties to its child components. 165 | 166 | ### Prop Drilling 167 | 168 | Props drilling refers to the practice of passing props through multiple components in order to reach a particular component that needs the property. Therefore, the intermediate components act only as a bridge to deliver the data to the target component. For example, consider an App to create a ``Gallery`` that renders images and allows comments in each one (see Figure bellow). First, the App renders ``Gallery`` passing the user and avatar props. Then, the ``Image`` renders the ``Comment`` components, also passing the ``user`` and ``avatar`` props. Finally, ``Comment`` renders ``Author``, passing again the ``user`` and ``avatar`` props. In other words, ``Author`` is the only component that really needs to use these props. 169 | 170 | Example of Prop Drilling (code smell) 171 | 172 | 173 | 174 | As the codebase increases, Prop Drilling makes it challenging to figure out where the data is initialized, updated, or used. Furthermore, it results in tightly coupled components. On the other hand, there are alternatives to avoid prop drillings, such as component composition and Context API. Since each component is usually in a separate file, in our ``Gallery`` example (see Figure), there are five different files to check for property updates, including, Author.jsx, Comment.jsx, Image.jsx, Gallery.jsx, and the file that calls the ``Gallery`` component, App.jsx. Moreover, Prop Drilling results in tightly coupled components. For example, whenever the Author component needs more props from the top, all the intermediate levels must be updated. Finally, it is worth mentioning that React documentation recommends using component composition: 175 | 176 | > _If you only want to avoid passing some props through many levels, component composition is often a simpler solution than context._ 177 | 178 | 179 | ## Traditional Smells 180 | 181 | In the grey literature and the interviews with developers, we identified six smells that are similar to traditional smells: Inheritance instead of Composition (since the inverse relation is considered a good object-oriented principle), Duplicated Component (which is a particular case of Duplicated Code), Large File, Large Component (which can be considered a particular case of the traditional Large Class), and Low Cohesion. We also consider that Too Many Props is similar to Data Class and Large Class proposed by Martin Fowler. A Data Class has mostly data and only setter and getter methods. A Large Class is a class with several responsibilities. On the other hand, Too Many Props designates a component with a large number of props. Essentially, this smell is very similar to the well-kwon Long Parameter List smell, proposed by Fowler. Just to clarify, in React, properties designates the parameters passed into components. 182 | 183 | ### Inheritance instead of Composition 184 | 185 | In React, developers tend to use inheritance to tackle two problems: (1) to express containment relations, particularly when a component does not know their possible children; (2) to express specialization relations, when components are ''special cases'' of other components. However, as usual in object-oriented design, React developers recommend using composition over inheritance whenever possible. 186 | 187 | For example, consider a page that handles ``Employee`` and a special case called ``Developer``, which has a ``level`` and receives a ``bonus``. A possible design consists of creating a generic form to receive data common to all employees and reuse it in the form that handles ``Developer`` data, as in the following code: 188 | 189 | ```jsx 190 | class Employee extends React.Component { 191 | render() { 192 | return (
Name: {this.props.name}
); 193 | } 194 | } 195 | ``` 196 | 197 | ```jsx 198 | class Developer extends Employee { 199 | render() { 200 | return ( 201 |
202 | |\colorbox{yellow}{\{super.render()\}}| 203 | Level: 204 | Bonus: 205 |
206 | ) 207 | } 208 | } 209 | ``` 210 | 211 | However, inheritance results in a tight coupling between components. For example, changes in the base component can affect all child components in unpredictable ways. On the other hand, by using composition instead of inheritance, we can reuse only the UI behavior, as in the following example: 212 | 213 | ```jsx 214 | class Developer extends React.Component { 215 | render() { 216 | return ( 217 |
218 | 219 | Level: 220 | Bonus: 221 |
222 | ) 223 | } 224 | } 225 | ``` 226 | 227 | ### Duplicated Component 228 | This smell refers to almost identical components. For example, the problem usually occurs when multiple developers extract the same UI code to different components are unaware of an existing component and reimplement it. In the following example, {\tt Comment} and {\tt Opinion} have the same code. 229 | 230 | ```jsx 231 | function Comment(props) { 232 | return ( 233 |
234 | 235 |
{props.text}
236 |
{props.date}
237 |
238 | ); 239 | } 240 | ``` 241 | 242 | ```jsx 243 | function Opinion(props) { 244 | return ( 245 |
246 | 247 |
{props.text}
248 |
{props.date}
249 |
250 | ); 251 | } 252 | ``` 253 | 254 | ### Too Many Props 255 | 256 | Props (React stands for properties) are arguments passed to components via HTML attributes. However, it is hard to understand components with a long list of props. For example, consider the ``Comment`` component used as an example in React’s documentation. This component has many properties, including the four properties presented in the following code: 257 | 258 | ```jsx 259 | function Comment(props) { 260 | return ( 261 |
262 |
{props.name}
263 | 264 |
{props.text}
265 |
{props.date}
266 | // other props 267 |
268 | ); 269 | } 270 | ``` 271 | 272 | To reduce the number of props handled by ``Comment``, we can extract the props related to avatars (i.e., ``name`` and ``avatarUrl``) to a new component, called ``Avatar``. Then, ``Comment`` just references this new component, as in the following code: 273 | 274 | ```jsx 275 | function Comment(props) { 276 | return ( 277 |
278 | 279 |
{props.text}
280 |
{props.date}
281 |
282 | ); 283 | } 284 | ``` 285 | 286 | ### Large Component 287 | Clean and small components improve readability and maintainability. For example, \textsc{React} documentation provides this recommendation for refactoring large components 288 | 289 | > If a part of your UI is used several times, or is complex enough on its own, it is a good candidate to be extracted to a separate component. 290 | 291 | 292 | ### Large Files 293 | 294 | A large file is one with several components whose implementation requires several lines of code. Indeed, some developers advocate that we should have exactly one component per file. See for example the following comment from one the interviewed developers: 295 | 296 | > I got tired of trying to fix a bug on a component co-located with other six components in a file with thousands of lines. 297 | 298 | ### Low Cohesion 299 | 300 | As usual in software design, the implementation of components must be cohesive and follow the Single Responsibility Principle. In other words, each component's data and behavior must be related to make the component reusable and easy to understand. 301 | 302 | ## About 303 | 304 | This catalog was proposed by Fabio Ferreira and Marco Tulio Valente, from [ASERG/DCC/UFMG][ASERG]. 305 | 306 | Please feel free to make pull requests and suggestions ([Issues][Issues] tab). 307 | 308 | 309 | [React]: https://reactjs.org 310 | [Facebook]: https://github.com/facebook 311 | [ASERG]: http://aserg.labsoft.dcc.ufmg.br/ 312 | [Issues]: https://github.com/fabiosferreira/React-Code-Smells/issues 313 | [eslint-plugin-react]: https://github.com/yannickcr/eslint-plugin-react 314 | 315 |
316 | -------------------------------------------------------------------------------- /img/prop_drilling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabiosferreira/React-Code-Smells/9de677187ef16c47e576174482b0b62a0c4d49d4/img/prop_drilling.png --------------------------------------------------------------------------------