Original article: https://antongunnarsson.com/react-component-code-smells/
What is Code Smell? A code smell is something that can indicate a problem deeper within the code, but not necessarily a bug. Read more on Wikipedia
1. Too many props
A component with too many props is a sign that the component should be broken up.
So how much is too much? That depends .
You can see a component with 20 props and be satisfied it’s still working fine, now you want to add one more to the already long list of props, there are a few things to consider:
Does this component do many things?
Like functions, components should only do one thing well, so always check if it’s possible to split the component into multiple child components?
Should I use composition?
A good but often overlooked pattern is to create compose components instead of handling all the logic within it. Suppose we have a component like this:
1 2 3 4 5 6 7 8 9 10 | <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> <span class="token class-name">ApplicationForm</span></span> <span class="token attr-name">user</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> userData <span class="token punctuation">}</span></span> <span class="token attr-name">organization</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> organizationData <span class="token punctuation">}</span></span> <span class="token attr-name">categories</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> categoriesData <span class="token punctuation">}</span></span> <span class="token attr-name">locations</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> locationsData <span class="token punctuation">}</span></span> <span class="token attr-name">onSubmit</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> handleSubmit <span class="token punctuation">}</span></span> <span class="token attr-name">onCancel</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> handleCancel <span class="token punctuation">}</span></span> <span class="token attr-name">...</span> <span class="token punctuation">/></span></span> |
Looking at the props of this component, we can see that they are all related to the functionality of the component, but there is still room to improve this by passing in some child components instead:
1 2 3 4 5 6 7 | <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> <span class="token class-name">ApplicationForm</span></span> <span class="token attr-name">onSubmit</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> handleSubmit <span class="token punctuation">}</span></span> <span class="token attr-name">onCancel</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> handleCancel <span class="token punctuation">}</span></span> <span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> <span class="token class-name">UserField</span></span> <span class="token attr-name">user</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> userData <span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> <span class="token class-name">OrganizationField</span></span> <span class="token attr-name">organization</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> organizationData <span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> <span class="token class-name">CategoryField</span></span> <span class="token attr-name">categories</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> categoriesData <span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> <span class="token class-name">LocationsField</span></span> <span class="token attr-name">locations</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> locationsData <span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span> <span class="token class-name">ApplicationForm</span></span> <span class="token punctuation">></span></span> |
Now we’ve made sure that the ApplicationForm
only handles onSubmit
and onCancel
. Child components can handle anything related to their part of the bigger picture. This is also a great opportunity to use React Context for parent-child component communication.
Read more about compound components in React .
Am I sending too many config props?
In some cases, it’s a good idea to group props together into an object, for example to make swapping this configuration easier.
1 2 3 4 5 6 7 8 9 10 11 | <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> <span class="token class-name">Grid</span></span> <span class="token attr-name">data</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> gridData <span class="token punctuation">}</span></span> <span class="token attr-name">pagination</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> <span class="token boolean">false</span> <span class="token punctuation">}</span></span> <span class="token attr-name">autoSize</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> <span class="token boolean">true</span> <span class="token punctuation">}</span></span> <span class="token attr-name">enableSort</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> <span class="token boolean">true</span> <span class="token punctuation">}</span></span> <span class="token attr-name">sortOrder</span> <span class="token attr-value"><span class="token punctuation attr-equals">=</span> <span class="token punctuation">"</span> desc <span class="token punctuation">"</span></span> <span class="token attr-name">disableSelection</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> <span class="token boolean">true</span> <span class="token punctuation">}</span></span> <span class="token attr-name">infiniteScroll</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> <span class="token boolean">true</span> <span class="token punctuation">}</span></span> <span class="token attr-name">...</span> <span class="token punctuation">/></span></span> |
All props except data
can be considered as config. In these cases, you should change the Grid component so that it receives an option that options
the above configs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token keyword">const</span> options <span class="token operator">=</span> <span class="token punctuation">{</span> pagination <span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">,</span> autoSize <span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">,</span> enableSort <span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">,</span> sortOrder <span class="token operator">:</span> <span class="token string">'desc'</span> <span class="token punctuation">,</span> disableSelection <span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">,</span> infiniteScroll <span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">,</span> <span class="token operator">...</span> <span class="token punctuation">}</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> <span class="token class-name">Grid</span></span> <span class="token attr-name">data</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> gridData <span class="token punctuation">}</span></span> <span class="token attr-name">options</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> options <span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span> |
2. Incompatible props
Avoid sending props that are incompatible with each other.
For example we create a generic <Input />
component that aims to handle type=text
, but after a while we add functionality to it with type=tel
. The implementation could be as follows:
1 2 3 4 5 6 | <span class="token keyword">function</span> <span class="token function">Input</span> <span class="token punctuation">(</span> <span class="token parameter"><span class="token punctuation">{</span> value <span class="token punctuation">,</span> isPhoneNumberInput <span class="token punctuation">,</span> autoCapitalize <span class="token punctuation">}</span></span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> autoCapitalize <span class="token punctuation">)</span> <span class="token function">capitalize</span> <span class="token punctuation">(</span> value <span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> input</span> <span class="token attr-name">value</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> value <span class="token punctuation">}</span></span> <span class="token attr-name">type</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> isPhoneNumberInput <span class="token operator">?</span> <span class="token string">'tel'</span> <span class="token operator">:</span> <span class="token string">'text'</span> <span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span> <span class="token punctuation">}</span> |
The problem is that isPhoneNumberInput
and autoCapitalize
don’t match. We can’t capitalize phone numbers.
In this case, the solution is probably to split it into many smaller components. If we have some logic to share, let’s move it to a custom hook :
1 2 3 4 5 6 7 8 9 10 11 12 13 | <span class="token keyword">function</span> <span class="token function">TextInput</span> <span class="token punctuation">(</span> <span class="token parameter"><span class="token punctuation">{</span> value <span class="token punctuation">,</span> autoCapitalize <span class="token punctuation">}</span></span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> autoCapitalize <span class="token punctuation">)</span> <span class="token function">capitalize</span> <span class="token punctuation">(</span> value <span class="token punctuation">)</span> <span class="token function">useSharedInputLogic</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> input</span> <span class="token attr-name">value</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> value <span class="token punctuation">}</span></span> <span class="token attr-name">type</span> <span class="token attr-value"><span class="token punctuation attr-equals">=</span> <span class="token punctuation">'</span> text <span class="token punctuation">'</span></span> <span class="token punctuation">/></span></span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">PhoneNumberInput</span> <span class="token punctuation">(</span> <span class="token parameter"><span class="token punctuation">{</span> value <span class="token punctuation">}</span></span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">useSharedInputLogic</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> input</span> <span class="token attr-name">value</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> value <span class="token punctuation">}</span></span> <span class="token attr-name">type</span> <span class="token attr-value"><span class="token punctuation attr-equals">=</span> <span class="token punctuation">'</span> tel <span class="token punctuation">'</span></span> <span class="token punctuation">/></span></span> <span class="token punctuation">}</span> |
While this example is a bit convoluted, finding props that are not compatible with each other is often a good indication that you should check if the component needs to be broken down.
3. Copying props into state
Don’t block the flow of data by copying props into state.
1 2 3 4 5 6 | <span class="token keyword">function</span> <span class="token function">Button</span> <span class="token punctuation">(</span> <span class="token parameter"><span class="token punctuation">{</span> text <span class="token punctuation">}</span></span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">[</span> buttonText <span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span> <span class="token punctuation">(</span> text <span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> button</span> <span class="token punctuation">></span></span> <span class="token punctuation">{</span> buttonText <span class="token punctuation">}</span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span> button</span> <span class="token punctuation">></span></span> <span class="token punctuation">}</span> |
By putting props text
in the initialValue of useState causes the component to ignore updates of props text
. If the props change, the component still renders with the first value, for most props this is unexpected behavior, which can make the component more prone to errors.
A more practical example of this is when we want to get some new value from a value, and especially if this requires some slow computation. In the example below, we run the slowFormatText
function to format props text
, which takes a long time to execute.
1 2 3 4 5 6 | <span class="token keyword">function</span> <span class="token function">Button</span> <span class="token punctuation">(</span> <span class="token parameter"><span class="token punctuation">{</span> text <span class="token punctuation">}</span></span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">[</span> formattedText <span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span> <span class="token punctuation">(</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">slowlyFormatText</span> <span class="token punctuation">(</span> text <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> button</span> <span class="token punctuation">></span></span> <span class="token punctuation">{</span> formattedText <span class="token punctuation">}</span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span> button</span> <span class="token punctuation">></span></span> <span class="token punctuation">}</span> |
A better way to deal with this is to use the useMemo hook to remember the results:
1 2 3 4 5 6 | <span class="token keyword">function</span> <span class="token function">Button</span> <span class="token punctuation">(</span> <span class="token parameter"><span class="token punctuation">{</span> text <span class="token punctuation">}</span></span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> formattedText <span class="token operator">=</span> <span class="token function">useMemo</span> <span class="token punctuation">(</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">slowlyFormatText</span> <span class="token punctuation">(</span> text <span class="token punctuation">)</span> <span class="token punctuation">,</span> <span class="token punctuation">[</span> text <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> button</span> <span class="token punctuation">></span></span> <span class="token punctuation">{</span> formattedText <span class="token punctuation">}</span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span> button</span> <span class="token punctuation">></span></span> <span class="token punctuation">}</span> |
Now slowlyFormatText
runs only when the text
changes and we don’t block component update.
Sometimes we still need the prop where the changes are ignored, for example the color picker has given options, but when the user changes it, we don’t want to change it immediately, just save it temporarily. In this case it is perfectly fine to copy the prop into the state, but to represent this behavior of the user we can split it to initialColor or defaultColor
Further reading: Writing resilient components by Dan Abramov .
4. Returning JSX from functions
Don’t return JSX from functions inside the component.
This is a pattern that has largely disappeared as function components become more common, but I still come across it from time to time.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <span class="token keyword">function</span> <span class="token function">Component</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token function-variable function">topSection</span> <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> header</span> <span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> h1</span> <span class="token punctuation">></span></span> <span class="token plain-text">Component header</span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span> h1</span> <span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span> header</span> <span class="token punctuation">></span></span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> <span class="token function-variable function">middleSection</span> <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> main</span> <span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> p</span> <span class="token punctuation">></span></span> <span class="token plain-text">Some text</span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span> p</span> <span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span> main</span> <span class="token punctuation">></span></span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> <span class="token function-variable function">bottomSection</span> <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> footer</span> <span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> p</span> <span class="token punctuation">></span></span> <span class="token plain-text">Some footer text</span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span> p</span> <span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span> footer</span> <span class="token punctuation">></span></span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> div</span> <span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token function">topSection</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">{</span> <span class="token function">middleSection</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">{</span> <span class="token function">bottomSection</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span> div</span> <span class="token punctuation">></span></span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> |
While this may be okay at first, it makes it difficult to reason about the code, is discouraged, and should be avoided. To solve this problem you should split these into subcomponents instead.
Remember that when you create a new component, you don’t need to split the new file. Sometimes you should keep multiple components in the same file if they are tightly coupled.
5. Multiple booleans for state
Avoid using multiple state booleans to represent the state of the component
When writing a component and extending its functionality, you have multiple states to indicate which state the component is in. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <span class="token keyword">function</span> <span class="token function">Component</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">[</span> isLoading <span class="token punctuation">,</span> setIsLoading <span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span> <span class="token punctuation">(</span> <span class="token boolean">false</span> <span class="token punctuation">)</span> <span class="token keyword">const</span> <span class="token punctuation">[</span> isFinished <span class="token punctuation">,</span> setIsFinished <span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span> <span class="token punctuation">(</span> <span class="token boolean">false</span> <span class="token punctuation">)</span> <span class="token keyword">const</span> <span class="token punctuation">[</span> hasError <span class="token punctuation">,</span> setHasError <span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span> <span class="token punctuation">(</span> <span class="token boolean">false</span> <span class="token punctuation">)</span> <span class="token keyword">const</span> <span class="token function-variable function">fetchSomething</span> <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">setIsLoading</span> <span class="token punctuation">(</span> <span class="token boolean">true</span> <span class="token punctuation">)</span> <span class="token function">fetch</span> <span class="token punctuation">(</span> url <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">then</span> <span class="token punctuation">(</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">setIsLoading</span> <span class="token punctuation">(</span> <span class="token boolean">false</span> <span class="token punctuation">)</span> <span class="token function">setIsFinished</span> <span class="token punctuation">(</span> <span class="token boolean">true</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">catch</span> <span class="token punctuation">(</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">setHasError</span> <span class="token punctuation">(</span> <span class="token boolean">true</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> isLoading <span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> <span class="token class-name">Loader</span></span> <span class="token punctuation">/></span></span> <span class="token keyword">if</span> <span class="token punctuation">(</span> hasError <span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> <span class="token class-name">Error</span></span> <span class="token punctuation">/></span></span> <span class="token keyword">if</span> <span class="token punctuation">(</span> isFinished <span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> <span class="token class-name">Success</span></span> <span class="token punctuation">/></span></span> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> button</span> <span class="token attr-name">onClick</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> fetchSomething <span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span> <span class="token punctuation">}</span> |
Although it’s technically working fine, it’s hard to reason about the state of the component, it can go wrong. We can fall into impossible state if we accidentally set both isLoading
and isFinished
to true at the same time.
A better way to manage that state is to use enum
. In other languages enum is a way to declare a set of constant values, since enums don’t exist in javascript we can use string instead.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <span class="token keyword">function</span> <span class="token function">Component</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">[</span> state <span class="token punctuation">,</span> setState <span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span> <span class="token punctuation">(</span> <span class="token string">'idle'</span> <span class="token punctuation">)</span> <span class="token keyword">const</span> <span class="token function-variable function">fetchSomething</span> <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">setState</span> <span class="token punctuation">(</span> <span class="token string">'loading'</span> <span class="token punctuation">)</span> <span class="token function">fetch</span> <span class="token punctuation">(</span> url <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">then</span> <span class="token punctuation">(</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">setState</span> <span class="token punctuation">(</span> <span class="token string">'finished'</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">catch</span> <span class="token punctuation">(</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">setState</span> <span class="token punctuation">(</span> <span class="token string">'error'</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> state <span class="token operator">===</span> <span class="token string">'loading'</span> <span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> <span class="token class-name">Loader</span></span> <span class="token punctuation">/></span></span> <span class="token keyword">if</span> <span class="token punctuation">(</span> state <span class="token operator">===</span> <span class="token string">'error'</span> <span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> <span class="token class-name">Error</span></span> <span class="token punctuation">/></span></span> <span class="token keyword">if</span> <span class="token punctuation">(</span> state <span class="token operator">===</span> <span class="token string">'finished'</span> <span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> <span class="token class-name">Success</span></span> <span class="token punctuation">/></span></span> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> button</span> <span class="token attr-name">onClick</span> <span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span> <span class="token punctuation">{</span> fetchSomething <span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span> <span class="token punctuation">}</span> |
Doing it this way we removed the impossible state
and made the component more understandable. If you use TypeScript, you can declare it like this:
1 2 | <span class="token keyword">const</span> <span class="token punctuation">[</span> state <span class="token punctuation">,</span> setState <span class="token punctuation">]</span> <span class="token operator">=</span> useState <span class="token operator"><</span> <span class="token string">'idle'</span> <span class="token operator">|</span> <span class="token string">'loading'</span> <span class="token operator">|</span> <span class="token string">'error'</span> <span class="token operator">|</span> <span class="token string">'finished'</span> <span class="token operator">></span> <span class="token punctuation">(</span> <span class="token string">'idle'</span> <span class="token punctuation">)</span> |
6. Too many useState
Avoid using too much useState
in the component.
A component with too much useState
is like doing too many things in it, it is better to split it into multiple components, however there will be complex components that need a lot of state.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <span class="token keyword">function</span> <span class="token function">AutocompleteInput</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">[</span> isOpen <span class="token punctuation">,</span> setIsOpen <span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span> <span class="token punctuation">(</span> <span class="token boolean">false</span> <span class="token punctuation">)</span> <span class="token keyword">const</span> <span class="token punctuation">[</span> inputValue <span class="token punctuation">,</span> setInputValue <span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span> <span class="token punctuation">(</span> <span class="token string">''</span> <span class="token punctuation">)</span> <span class="token keyword">const</span> <span class="token punctuation">[</span> items <span class="token punctuation">,</span> setItems <span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span> <span class="token punctuation">(</span> <span class="token punctuation">[</span> <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token keyword">const</span> <span class="token punctuation">[</span> selectedItem <span class="token punctuation">,</span> setSelectedItem <span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span> <span class="token punctuation">(</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token keyword">const</span> <span class="token punctuation">[</span> activeIndex <span class="token punctuation">,</span> setActiveIndex <span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span> <span class="token punctuation">(</span> <span class="token operator">-</span> <span class="token number">1</span> <span class="token punctuation">)</span> <span class="token keyword">const</span> <span class="token function-variable function">reset</span> <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">setIsOpen</span> <span class="token punctuation">(</span> <span class="token boolean">false</span> <span class="token punctuation">)</span> <span class="token function">setInputValue</span> <span class="token punctuation">(</span> <span class="token string">''</span> <span class="token punctuation">)</span> <span class="token function">setItems</span> <span class="token punctuation">(</span> <span class="token punctuation">[</span> <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token function">setSelectedItem</span> <span class="token punctuation">(</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token function">setActiveIndex</span> <span class="token punctuation">(</span> <span class="token operator">-</span> <span class="token number">1</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> <span class="token function-variable function">selectItem</span> <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token parameter">item</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">setIsOpen</span> <span class="token punctuation">(</span> <span class="token boolean">false</span> <span class="token punctuation">)</span> <span class="token function">setInputValue</span> <span class="token punctuation">(</span> item <span class="token punctuation">.</span> name <span class="token punctuation">)</span> <span class="token function">setSelectedItem</span> <span class="token punctuation">(</span> item <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token operator">...</span> <span class="token punctuation">}</span> |
We have reset
function to reset states, selectItem
function to update some state. Both of these functions pretty much use setState
to do the task. Now, if we have more actions that have to update the state, this becomes difficult to keep error free in the long run. In these cases, it is beneficial to manage the state with a useReducer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | <span class="token keyword">const</span> initialState <span class="token operator">=</span> <span class="token punctuation">{</span> isOpen <span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">,</span> inputValue <span class="token operator">:</span> <span class="token string">""</span> <span class="token punctuation">,</span> items <span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">]</span> <span class="token punctuation">,</span> selectedItem <span class="token operator">:</span> <span class="token keyword">null</span> <span class="token punctuation">,</span> activeIndex <span class="token operator">:</span> <span class="token operator">-</span> <span class="token number">1</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">reducer</span> <span class="token punctuation">(</span> <span class="token parameter">state <span class="token punctuation">,</span> action</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span> action <span class="token punctuation">.</span> type <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">"reset"</span> <span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span> initialState <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">"selectItem"</span> <span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span> state <span class="token punctuation">,</span> isOpen <span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">,</span> inputValue <span class="token operator">:</span> action <span class="token punctuation">.</span> payload <span class="token punctuation">.</span> name <span class="token punctuation">,</span> selectedItem <span class="token operator">:</span> action <span class="token punctuation">.</span> payload <span class="token punctuation">}</span> <span class="token keyword">default</span> <span class="token operator">:</span> <span class="token keyword">throw</span> <span class="token function">Error</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">AutocompleteInput</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">[</span> state <span class="token punctuation">,</span> dispatch <span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useReducer</span> <span class="token punctuation">(</span> reducer <span class="token punctuation">,</span> initialState <span class="token punctuation">)</span> <span class="token keyword">const</span> <span class="token function-variable function">reset</span> <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">dispatch</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> type <span class="token operator">:</span> <span class="token string">'reset'</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> <span class="token function-variable function">selectItem</span> <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token parameter">item</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">dispatch</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> type <span class="token operator">:</span> <span class="token string">'selectItem'</span> <span class="token punctuation">,</span> payload <span class="token operator">:</span> item <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token operator">...</span> <span class="token punctuation">}</span> |
With the use of useReducer
we have encapsulated the state management logic and moved the complexity out of the component. This makes it easier to understand what’s going on, we have separate UI and logic.
Both useState and useReducer have their pros and cons for different use cases, one of my favorites with reducers is Kent C. Dodds’ state reducer pattern.
7. Large useEffect
Avoid using large useEffect
and do multiple things. It makes the code difficult to find bugs, keep updating.
One mistake I made was putting too many things in one useEffect
.
1 2 3 4 5 6 7 8 9 10 11 12 13 | <span class="token keyword">function</span> <span class="token function">Post</span> <span class="token punctuation">(</span> <span class="token parameter"><span class="token punctuation">{</span> id <span class="token punctuation">,</span> unlisted <span class="token punctuation">}</span></span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">...</span> <span class="token function">useEffect</span> <span class="token punctuation">(</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">fetch</span> <span class="token punctuation">(</span> <span class="token template-string"><span class="token template-punctuation string">`</span> <span class="token string">/posts/</span> <span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span> id <span class="token interpolation-punctuation punctuation">}</span></span> <span class="token template-punctuation string">`</span></span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">then</span> <span class="token punctuation">(</span> <span class="token comment">/* do something */</span> <span class="token punctuation">)</span> <span class="token function">setVisibility</span> <span class="token punctuation">(</span> unlisted <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token punctuation">[</span> id <span class="token punctuation">,</span> unlisted <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token operator">...</span> <span class="token punctuation">}</span> |
useEffect
is not very big but does a lot of work. If the id
changes then fetch the post, if unlisted
changes then setVisibility, but as long as one of the two dependencies changes, both will work.
For easy tracking we should split into two useEffect
with separate dependencies
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token keyword">function</span> <span class="token function">Post</span> <span class="token punctuation">(</span> <span class="token parameter"><span class="token punctuation">{</span> id <span class="token punctuation">,</span> unlisted <span class="token punctuation">}</span></span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">...</span> <span class="token function">useEffect</span> <span class="token punctuation">(</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// when id changes fetch the post</span> <span class="token function">fetch</span> <span class="token punctuation">(</span> <span class="token template-string"><span class="token template-punctuation string">`</span> <span class="token string">/posts/</span> <span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span> id <span class="token interpolation-punctuation punctuation">}</span></span> <span class="token template-punctuation string">`</span></span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">then</span> <span class="token punctuation">(</span> <span class="token comment">/* do something */</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token punctuation">[</span> id <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token function">useEffect</span> <span class="token punctuation">(</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// when unlisted changes update visibility</span> <span class="token function">setVisibility</span> <span class="token punctuation">(</span> unlisted <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token punctuation">[</span> unlisted <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token operator">...</span> <span class="token punctuation">}</span> |
This way we reduce the complexity of the component, make it easier to guess the logic, and reduce the risk of errors.
8. Wrapping up
Alright, that’s it for now! Remember that the foregoing is not a rule but an indication that something may be “wrong”. You will definitely encounter the above situations and want to make corrections.
Would you like feedback or suggestions for other code smells
? Find me on Twitter!