Writing cleaner React code for better maintainability and testability
Writing maintainable code should be the number one goal while developing. What do we mean by maintainable code
- Testable and properly Tested via UTs
- Readable by anyone
- Easy to change
Let’s look at the below code and make it more maintainable
interface IGetStrengthPercentageProps {
network: string;
ip: string;
condition: number;
}
interface INetworkDetailsProps {
networkType?: string,
ipAddress?: string,
condition?: number,
}
const NetworkDetails = (props?: INetworkDetailsProps) => {
const percentProps = {
network : props?.networkType || "unknown",
ip : props?.ipAddress || "unknown",
condition: props?.condition || 0,
};
const percentage = getStrengthPercentage(percentProps);
return props ? (
<>
Network: {percentage && `strength ${percentage} %`} {props.networkType} {props.ipAddress}
</>
) : (<>'No Network data'</>) ;
}
😱 wait this can be better. You are joking right
Before we begin. Name of several functions can be better so suggest in comment ;)
First, remove the ternary operator
const NetworkDetails = (props?: INetworkDetailsProps) => {
if (!props) {
return (<>'No Network data'</>);
}
......
return (
<>
Network: {percentage && `strength ${percentage} %`} {props.networkType} {props.ipAddress}
</>
);
}
Early exit makes the code more readable. It also ensures code doesn’t break if the object is undefined. We can also remove the ? (optional chaining operator), if we want, in subsequent code now that the props is always defined.
Move the transformation to a function
function fillMissingProps(props: INetworkDetailsProps) {
const percentProps = {
network : props.networkType || "unknown",
ip : props.ipAddress || "unknown",
condition: props.condition || 0,
};
return percentProps;
}
const NetworkDetails = (props?: INetworkDetailsProps) => {
....
const percentProps = fillMissingProps(props);
const percentage = getStrengthPercentage(percentProps);
....
}
Now the code is more readable. The function is also smaller. Again we have removed the optional chaining as the object can never be undefined.
Clean up the JSX.
interface IPercentageTextProps {
percentage: number
}
const PercentageText = ({percentage}: IPercentageTextProps) => {
if (percentage === undefined) {
return <></>;
}
return (
<>strength {percentage} %</>
);
}
const NetworkDetails = (props?: INetworkDetailsProps) => {
.....
return (
<>
Network:
<PercentageText percentage={percentage} />
{" "}{props.networkType}
{" "}{props.ipAddress}
</>
);
}
By splitting the percentage logic we are not only making it more testable but also making the code cleaner to read. For numbers, && doesn’t work; as 0 equates to false hence the explicit undefined check.
We split each printed prop on new line for readability. But now we need to add an explicit space.
Fix the optional properties
interface INetworkDetailsProps {
networkType: string | undefined,
ipAddress: string | undefined,
condition: number | undefined,
}
The reason we marked these properties with ? is to let typescript know that it can be undefined. But by doing this typescript won’t throw an error if we skip any of these properties. So we remove the ? and replace it with union type and let typescript know that this can be undefined but its required.
Add return types
We are missing the return type everywhere. Let’s add a return type to every function.
type TNetworkDetails = (props?: INetworkDetailsProps) => JSX.Element;
const NetworkDetails: TNetworkDetails = (props) => {
....
}
type TPercentageText = (props: IPercentageTextProps) => JSX.Element;
const PercentageText: TPercentageText = ({percentage}) => {
....
}
function fillMissingProps(props: INetworkDetailsProps): IGetStrengthPercentageProps {
....
}
Add types for Objects
Adding types for objects is optional but it helps the editor with code completion
function fillMissingProps(props: INetworkDetailsProps): IGetStrengthPercentageProps {
const percentProps: IGetStrengthPercentageProps = {
....
};
return percentProps;
}
Give a displayName to our jsx function
This helps with snapshot tests else in the parent component when we do shallow( ) testing the name will be missing in the snapshot. (Newer versions on React/Jest might have fixed this)
const NetworkDetails = (props?: INetworkDetailsProps): JSX.Element | String => {
.....
}
NetworkDetails.displayName = "NetworkDetails"
Destructure some of the params
This is just to make the code more readable and explicit. Also now that the jsx is smaller we remove the spaces({ “ “}) and put all in one line
const NetworkDetails: TNetworkDetails = (props) => {
if (!props) {
return <>No Network data</>;
}
const { networkType, ipAddress, condition } = props;
const percentProps = fillMissingProps({ networkType, ipAddress, condition });
const percentage = getStrengthPercentage(percentProps);
return (
<>
Network:
<PercentageText percentage={percentage} /> {networkType} {ipAddress}
</>
);
};
Finally lets split code into logical blocks or files
function fillMissingProps(props: INetworkDetailsProps) {
const percentProps: IGetStrengthPercentageProps = {
network: props.networkType || "unknown",
ip: props.ipAddress || "unknown",
condition: props.condition || 0,
};
return percentProps;
}
interface IPercentageTextProps {
percentage: number;
}
type TPercentageText = (props: IPercentageTextProps) => JSX.Element;
const PercentageText: TPercentageText = ({ percentage }) => {
if (percentage === undefined) {
return <></>;
}
return <>strength ${percentage} %</>;
};
interface INetworkDetailsProps {
networkType: string | undefined;
ipAddress: string | undefined;
condition: number | undefined;
}
type TNetworkDetails = (props?: INetworkDetailsProps) => JSX.Element;
const NetworkDetails: TNetworkDetails = (props) => {
if (!props) {
return <>No Network data</>;
}
const { networkType, ipAddress, condition } = props;
const percentProps = fillMissingProps({ networkType, ipAddress, condition });
const percentage = getStrengthPercentage(percentProps);
return (
<>
Network:
<PercentageText percentage={percentage} /> {networkType} {ipAddress}
</>
);
};
Want us to write more
Hit claps if you liked it. It will encourage us to write more. Follow, for more posts. Comment below if you have any other suggestions or inputs.