React Worst Better Best practices for creating component

ReactJS component building approach

React has been a wonderful library for creating UI components for the Web, but have you ever wondered why the project was named React? That could be because each component reacts to the changes very fast.

In this article, we will talk about how we can develop a UI component with worst-better-best practices which will highlight the way a component renders on every change irrespective of which component triggers the change, and later we will talk about the trade-offs.

First & foremost, how we will get to know whenever a component renders? To make it visible, we can download the Chrome extension react dev tools which provides two different tabs in browser dev tools. one is Components and second is Profile.

React dev tools.png

Let's enable the visibility of component renders:

  1. Click on the Profile tab in the dev tools:
  2. Click on the gear icon

Profile- setting icon.png

  1. Check the Highlight updates when components render checkbox.

Highlight updates when component render.png

Once we're done with enabling Highlight updates when components render, let's see how a render is highlighted in the browser

Rendering Better Approach.gif

As you can see from the above gif every keypress in the textbox is highlighting all the components including the textbox, select, and radio buttons. Actually, every keypress triggers the onChange event and all the components in the Better approach card are being highlighted means every time it highlights, it renders on the browser. Even whenever any component in the Better Approach card is clicked then all other components are also being re-rendered.

But why do components re-render? There are two major reasons for this:

  1. Whenever there is a change in any props then the component will re-render.
  2. if a component is a child to its parent and not wrapped with React.memo and any of the state of the parent gets changed, however, those states are not being passed to the component as props then also component will re-render.

if this is clear to you, then let's move to a comparison among all the approaches:

Comparison

Let's see how every card reacts to all the events to its components.

All three Approach Render.gif

Entering Elon Musk in the text box of Worst Approach card is highlighting the entire card it means all the components are being re-rendered every time onChange event triggers and the same happens with Better Approach card as well but all the child components are highlighted separately because we created them as separate components, more about this later in the post.

On the other hand, in the Best Approach card, the same event did not trigger the re-rendering of other components and only the component which was interacted with got re-rendered.

All cards are showing the same behavior whether enter the text in the textbox, select a value from the dropdown or select any radio button.

Coding style

Let's dive into the code to understand it deeply.

Worst Approach

We have written the entire code in a single file, which means if any state gets changed then the return statement will be executed and the component will re-render again.

import React, { useCallback, useState } from 'react';

import { listOfCountry, listOfGender } from '../data/data';

export const WorstApproach = () => {
    const [name, setName] = useState('');
    const [gender, setGender] = useState('');
    const [country, setCountry] = useState('');
    const [communicationPreference, setCommunicationPreference] = useState('');

    const onChangeNameHandler = useCallback(event => {
        setName(event?.target?.value || '');
    }, []);

    const onClickSubmitHandler = useCallback(() => {
        const payload = {
            name,
            gender,
            country,
            communicationPreference
        };
        console.log(payload);
        // send the payload in API call
    }, [communicationPreference, name, gender, country]);

    return (
        <div className='card flex-column h-4vw'>
            <h3>Worst Approach</h3>
            <div>
                <label>
                    <strong>Name:</strong>
                </label>
                <br />
                <input type='text' value={name} onChange={onChangeNameHandler} />
            </div>
            <div>
                <label>
                    <strong>Gender:</strong>
                </label>
                <br />
                <select
                    value={gender}
                    onChange={event => {
                        setGender(event.target.value);
                    }}
                >
                    <option value='Select'>Select</option>
                    {listOfGender.map((value, index) => (
                        <option value={value} key={`${value}-${index}`}>
                            {value}
                        </option>
                    ))}
                </select>
            </div>
            <div>
                <label>
                    <strong>I am from:</strong>
                </label>
                <br />
                <select
                    value={country}
                    onChange={event => {
                        setCountry(event.target.value);
                    }}
                >
                    <option value='Select'>Select</option>
                    {listOfCountry.map((value, index) => (
                        <option value={value} key={`${value}-${index}`}>
                            {value}
                        </option>
                    ))}
                </select>
            </div>
            <div className='flex-column'>
                <label>
                    <input
                        type='radio'
                        name='travel'
                        value='Email'
                        onChange={event => {
                            setCommunicationPreference(event.target.value);
                        }}
                    />
                    Email
                </label>
                <label>
                    <input
                        type='radio'
                        name='travel'
                        value='SMS'
                        onChange={event => {
                            setCommunicationPreference(event.target.value);
                        }}
                    />
                    SMS
                </label>
                <label>
                    <input
                        type='radio'
                        name='travel'
                        value='Whatsapp'
                        onChange={event => {
                            setCommunicationPreference(event.target.value);
                        }}
                    />
                    Whatsapp
                </label>
            </div>
            <button onClick={onClickSubmitHandler}>Submit</button>
        </div>
    );
};

Better Approach

Better approach creates small components separately and combined them to make a compound component. It still does not help in preventing re-rendering but it provides a way to reuse the small components. Hence it increases the Reusability.

import React, { useCallback, useState } from 'react';

import { listOfCountry, listOfGender, listOfCommunication } from '../data/data';
import { RadioButton } from './components/RadioButton';
import { Text } from './components/Text';
import { Dropdown } from './components/Dropdown';

export const BetterApproach = () => {
    const [name, setName] = useState('');
    const [gender, setGender] = useState('');
    const [country, setCountry] = useState('');
    const [communicationPreference, setCommunicationPreference] = useState('');

    const onChangeNameHandler = useCallback(event => {
        setName(event?.target?.value || '');
    }, []);

    const onClickSubmitHandler = useCallback(() => {
        const payload = {
            name,
            gender,
            country,
            communicationPreference
        };
        console.log(payload);
        // send the payload in API call
    }, [name, gender, country, communicationPreference]);

    return (
        <div className='card flex-column h-4vw'>
            <h3>Better Approach</h3>
            <Text label='Name' type='text' value={name} onChangeHandler={onChangeNameHandler} />
            <Dropdown
                label='Gender'
                value={gender}
                options={listOfGender}
                onSelect={event => setGender(event.target.value)}
            />
            <Dropdown label='I am from' options={listOfCountry} onSelect={event => setCountry(event.target.value)} />
            <RadioButton
                name='communication-2'
                options={listOfCommunication}
                onChangeHandler={event => setCommunicationPreference(event.target.value)}
            />
            <button onClick={onClickSubmitHandler}>Submit</button>
        </div>
    );
};

Text

import React from 'react';

export const Text = ({ label, type, value, onChangeHandler }) => {
    return (
        <div>
            <label>
                <strong>{label}</strong>
            </label>
            <br />
            <input type={type} value={value} onChange={onChangeHandler} />
        </div>
    );
};

Dropdown

import React from 'react';

export const Dropdown = ({ label, options, onSelect, value }) => {
    return (
        <div>
            <label>
                <strong>{label}</strong>
            </label>
            <br />
            <select value={value} onChange={onSelect}>
                <option value='Select'>Select</option>
                {options.map((optionValue, index) => (
                    <option value={optionValue} key={`${optionValue}-${index}`}>
                        {optionValue}
                    </option>
                ))}
            </select>
        </div>
    );
};

Radio Button


import React from 'react';

export const RadioButton = ({ options, name, onChangeHandler }) => {
    return (
        <div className='flex-column'>
            {options.map((option, index) => (
                <label key={`${option}-${index}`}>
                    <input type='radio' name={name} value={option} onChange={onChangeHandler} />
                    {option}
                </label>
            ))}
        </div>
    );
};

Best Approach

Best Approach also creates small components and combines them to create a compound component but with some additional advantages

  • Components are self-fulfilled meaning components have their event handler within themselves so they are not dependent on the parent components for handling events like onChange, onClick, etc.

  • Components are wrapped with React.memo which ensures that component will not re-render until any of the props value gets changed.

These two advantages make the component more reusable and consistent across the application. Following the best approach in all the components make the application scalable and maintainable too.

import React, { useCallback, useState } from 'react';

import { listOfCountry, listOfGender, listOfCommunication } from '../data/data';
import { RadioButton } from './components/RadioButton';
import { Text } from './components/Text';
import { Dropdown } from './components/Dropdown';

export const BestApproach = () => {
    const [name, setName] = useState('');
    const [gender, setGender] = useState('');
    const [country, setCountry] = useState('');
    const [communicationPreference, setCommunication] = useState('');

    const onClickSubmitHandler = useCallback(() => {
        const payload = {
            name,
            gender,
            country,
            communicationPreference
        };
        console.log(payload);
        // send the payload in API call
    }, [communicationPreference, country, gender, name]);

    return (
        <div className='card flex-column h-4vw'>
            <h3>Best Approach</h3>
            <Text label='Name' updateParent={setName} />
            <Dropdown label='Gender' options={listOfGender} updateParent={setGender} />
            <Dropdown label='I am from' options={listOfCountry} updateParent={setCountry} />
            <RadioButton name='communication-1' options={listOfCommunication} updateParent={setCommunication} />
            <button onClick={onClickSubmitHandler}>Submit</button>
        </div>
    );
};

Text

import React, { useState, useCallback } from 'react';

export const Text = React.memo(({ label, updateParent }) => {
    const [value, setValue] = useState('');

    const onChangeHandler = useCallback(event => {
        setValue(event?.target?.value || '');
    }, []);

    const onBlurHandler = useCallback(() => {
        updateParent(value);
    }, [updateParent, value]);

    return (
        <div>
            <label>
                <strong>{label}</strong>
            </label>
            <br />
            <input type='text' value={value} onChange={onChangeHandler} onBlur={onBlurHandler} />
        </div>
    );
});

Dropdown

import React, { useCallback } from 'react';
import { useState } from 'react';

export const Dropdown = React.memo(({ label, options, updateParent }) => {
    const [value, setValue] = useState('');

    const onChangeHandler = useCallback(event => {
        setValue(event?.target?.value || '');
    }, []);

    const onBlurHandler = useCallback(() => {
        updateParent(value);
    }, [updateParent, value]);

    return (
        <div>
            <label>
                <strong>{label}</strong>
            </label>
            <br />
            <select value={value} onChange={onChangeHandler} onBlur={onBlurHandler}>
                <option value='Select'>Select</option>
                {options.map((optionValue, index) => (
                    <option value={optionValue} key={`${optionValue}-${index}`}>
                        {optionValue}
                    </option>
                ))}
            </select>
        </div>
    );
});

Radio Button


import React from 'react';

export const RadioButton = React.memo(({ options, name, updateParent }) => {
    return (
        <div className='flex-column'>
            {options.map((option, index) => (
                <label key={`${option}-${index}`}>
                    <input
                        type='radio'
                        name={name}
                        value={option}
                        onChange={event => updateParent(event.target.value)}
                    />
                    {option}
                </label>
            ))}
        </div>
    );
});

Trade-offs

Best approach is the best because it provides the solution to build a reusable component in an efficient manner but it also has some cons which are not liked by a few developers.

  1. You need to manage the state of the same variable twice first in the component itself and then in the parent component if required.
  2. The developer ends up creating multiple files for creating a compound component.
  3. The developer needs to write import statements, and propTypes multiple times in each component.

Conclusion

Best approach provides out-of-the-box compound component creation strategy with more advantages than the better and worst approaches.

Please find the Github link to the repo and running code running app.

This is my first post as a developer and I'm not so good at writing. So any feedback is most welcome.

Please let me know your thoughts in the comment about the post.