Repository
https://github.com/stonecoinproject/stonem-frontend
Pull Request
https://github.com/stonecoinproject/stonem-frontend/pull/49
Continuing with the development of the STONE masternodes web-client, our previous update added the masternodes manager screen alongside some very vital constructs and reusable architectural patterns.
However, for any software project to stay healthy, scale and remain relevant, smart and efficient practices must be adopted. The masternodes screen inadvertently added some poor code practices to the source base as evidenced in the CodeClimate report above.
The analytical report indicated the code contained as much as 3 code smells (poor practices) and 16 duplications. If not quickly and carefully tackled, these poor practices may quickly compound till you are staring down on a mountain of technical debt.
To tackle some of these issues, I got to work. I realized the 3 code smells detected were actually due to the unusually LOC (Lines of Code) length of methods contained in the wallet manager suite of components. To actively get this solved, I decided to split large functions into smaller functions so the below sample piece of code goes from this monumental entity:
const transactionItem:React.SFC<transactionItemProps> = ({
amount,
brand,
children,
date,
hasNegativeIndex,
title,
time,
...props }) => (
<ToggleProvider
render={({
isOn,
doToggle,
}) => (
<Card
border={2}
borderColor={ isOn ? theme.colors.blue : theme.colors.bordergray}
borderRadius={theme.radiusSizes[1]}
p={3}
onClick={doToggle}
style={{
cursor: 'pointer',
}}
{...props}
>
<Flex>
<Box
mr={3}
width={1 / 5}
>
<Image width={1} src={brand} />
</Box>
<Box width={1}>
<Box width={1}>
<Flex width={1}>
<CapsText
fontSize={3}
mb={3}
width={1 / 2}
>
{title}
</CapsText>
<CapsText
fontSize={3}
mb={3}
textAlign={'right'}
width={1 / 2}
>
{time}
</CapsText>
</Flex>
<Flex width={1}>
<CapsText
color={hasNegativeIndex ? 'red' : 'placeholdergray'}
fontSize={3}
mb={2}
width={1 / 2}
>
{amount}
</CapsText>
<CapsText
color={'placeholdergray'}
fontSize={3}
mb={2}
textAlign={'right'}
width={1 / 2}
>
{date}
</CapsText>
</Flex>
</Box>
</Box>
</Flex>
<Box style={{
height: isOn ? 'auto' : '0',
overflowY: 'hidden',
}}>
{children}
</Box>
</Card>
)} />
);
...to the much more sane smaller function:
const transactionItem:React.SFC<transactionItemProps> = (props) => {
return (
<ToggleProvider
render={({ isOn, doToggle }) => (
<Card
borderColor={ isOn ? theme.colors.blue : theme.colors.bordergray}
onClick={doToggle}
{...transactionItemCardStyles}
{...props}
>
<Flex>
{renderBrand(props.brand)}
<Box width={1}>
{renderHeading(props.title, props.time)}
{renderMetaInformation(props.amount, props.date, props.hasNegativeIndex)}
</Box>
</Flex>
<Box style={{
height: isOn ? 'auto' : '0',
overflowY: 'hidden',
}}>{props.children}</Box>
</Card>
)} />
);
};
With this strategy fully employed, I was able to eliminate all code smells reported by CodeClimate.
To take care of duplicates, I did a little research and realized a bulk of my code duplication came from writing code that iterates through an array and renders data from the array. This looked like a really good time to abstract this monotonous routine and I decided to employ Typescript generic React components that could be overloaded with props and use a custom renderer.
I created the src/generics
directory and added the GenericList.tsx
component.
import * as React from 'react';
export interface GenericListProps<T> {
/** Items to be members of the list. */
items: T[];
/** Callback method to render the items. Allows us delegate rendering for each consumer. */
itemRenderer: (item: T, index: Number) => React.ReactNode;
}
/**
* Generic class that serves as an abstraction for list item iterators.
*/
export default class GenericList<T> extends React.Component<GenericListProps<T>, {}> {
constructor (props:GenericListProps<T>) {
super(props);
}
render () {
const {
items,
itemRenderer,
} = this.props;
return (items.map(itemRenderer));
}
}
So running iterations now looks like this:
// Import the base generic list class
import { GenericList } from '../generics'
export class WalletTransactionItemList extends GenericList {}
Then in a consumer class.
// ...previous code
render () {
return (
<WalletTransactionItemList
items={app.transactionData}
itemRenderer={renderWalletManagerTransactionItem}
/>
)
}
And this handles our iterations and iterables pretty well.
With all these implemented, we were able to get a clean slate from CodeClimate which feels pretty good. Pictured below is the code health analysis from Code Climate.
What's next?
- Provide further code documentation for existing components.
- Create responsive, mobile-first enhancements for the client.
- Add authentication and coin node creation functionality.