rss

Lissel's code blogShort rambles about programming


Isolating third party frontend components

Best practices for keeping your code manageable

tl;dr

Put all third party components inside wrapper components that you create yourself. This decreases coupling, and might also improve readability and specificity.

function View() {
    return <MyOwnCheckbox title="Opt-in" />
}

/* Hide away dependency on BootstrapFormControl */

function MyOwnCheckbox(props) {
    return <div class="my-own-checkbox">
        <span>{props.title}</span>
        <BootstrapFormControl
            type="checkbox"
            size-adjustment="5"
            color-scheme="cyan"
            obnoxious-attribute="arthur-dent"
        />
    </div>;

}

The problem

Front-end is a fast moving landscape of technologies and add-ons. You will always be one step behind the latest version of library X or component Y.

The npm registry does keep all versions of software, so you might think to just stay on an old version. This runs into two problems:

This project seem to have BoostrapFormControl all over the place. I wonder what it does, and how to use it? The documentation disappeared two years ago...

Learning from backend developers

In backend systems, the repository pattern is common. Instead of having your main application code directly calling the database with sql-commands, a repository class is created. The repository class does all the sql-handling.

Using repository pattern

When coding the application logic, you are exposed to four nicely named functions. The Repository class shields you from the complexities of making SQL requests.

Without repository pattern

When coding the application logic, you are exposed to all the complexity of the SqlConnection class. Instead of focusing on cats, you'll be stuck thinking about SQL.

Even in a frontend project, you might have classes such as APIService or DataFetcher rather than calling fetch directly from your views or components.

Revisiting frontend

I don't know if it is the legacy of jQuery, but frontend code often tends to look like this:

function View() {
	return (<div>
		<p>Text, whatever</p>
		<WickedButtonControl
			type="color-picker"
			included-colors="red, green, magentayellow"
			size=40
			margins="20,42,02,13"
			some-other-stuff="true"
			do-the-smurf-setting="smurf" />
	</div>);
}

Here, the view is dependent on WickedButtonControl. This is the equivalent of coding directly against SqlConnection in the backend example.

What I suggest is to not use the third party WickedButtonControl class directly in your view. Instead, wrap it inside your own component:

function View() {
	return (<div>
		<p>Some info, whatever</p>
		<MyColorPicker colors="red, green, magentayellow" />
	</div>);
}
function MyColorPicker(props) {
	return <WickedButtonControl
			type="color-picker"
			included-colors={props.colors}
			size=40
			margins="20,42,02,13"
			some-other-stuff="true"
			do-the-smurf-setting="smurf" />
}

Easier to change

Let's say we want to remake the color picker, perhaps replace it with another library. Even if this new library expects different inputs, we don't have to change the views. We only have to change our own wrapper-class: MyColorPicker.

/* The same view code as before. */
function View() {
	return (<div>
		<p>Some info, whatever</p>
		<MyColorPicker colors="red, green, magentayellow" />
	</div>);
}

function MyColorPicker(props) {
/*
	We are going to use BootstrapColorPicker,
	which expects hexadecimal color codes instead of color names
	which were previously used.
*/
	let colorCodes = props.colors
		.split(",")
		.map(x => x.trim()) /* Remove any blank spaces */
		.map(colorNameToColorCode)

	return <BootstrapColorPicker
			colors={colorCodes}
			theme="whatever-some-theme" />;
}

function colorNameToColorCode(name) {
	if(name == "red") {
		return "#ff0000";
	} else if(name == "green") {
		return "#00ff00";
	} /* etc. */

	return "#ffffff";
}

Notice that just by looking at the usage and the props, we can get a pretty good idea of what the component is supposed to do, and what we have to change for the new implementation.

The props act as a specification telling you what is expected of MyColorPicker.

Reduced complexity

Let's say you are using BootstrapPopup, and that BootstrapPopup has 45 different possible settings and props. How many of these will you be using? How many will be relevant for the view code?

By inserting BootstrapPopup directly into your view, you are dragging in the complexity of 45 different possible settings. This makes you view harder to maintain.

Probably, your view only cares about a few things. Like popup text, title and icon. This is a great foundation for defining your own component:

function View() {
	return <MyOwnPopup title="hi" icon="warning" message="Welcome to the popup!" />;
}
function MyOwnPopup(props) {
	let iconIndex = props.icon == "warning" ? 1 : 0;

	return <BootstrapPopup
		title={props.title}
		icon={iconIndex}
		message={props.text}
		padding=20
		some-other-specific-option=true
		/>;
}

Notice how the view does not need to care about specific formatting or bootstrap settings. We have reduced the complexity of the view's code.