How to keep cleaner React components with object map?
Martin Belev
Posted on July 26, 2020
We will see one way to refactor our React components by replacing conditionals with object map. This is a favorite refactoring of mine because it makes the components easier to understand and extend, groups the logic in a single place, and requires fewer lines of code.
Simple conditionals example
A lot of times based on some input we want to display different information to our users. Let's see an example which should make things clearer:
import React from 'react';
interface Props {
errorField: 'name' | 'email' | 'password' | 'date';
}
const ErrorMessageWithSwitch: React.FC<Props> = ({ errorField }) => {
switch (errorField) {
case 'name':
return <div>Please enter valid name.</div>;
case 'email':
return <div>Please enter valid email address.</div>;
case 'password':
return <div>Please enter valid password.</div>;
case 'date':
return <div>Please enter valid date of birth.</div>;
default:
return <div>Invalid field.</div>;
}
};
const ErrorMessageWithIf: React.FC<Props> = ({ errorField }) => {
if (errorField === 'name') {
return <div>Please enter valid name.</div>;
}
if (errorField === 'email') {
return <div>Please enter valid email address.</div>;
}
if (errorField === 'password') {
return <div>Please enter valid password.</div>;
}
if (errorField === 'date') {
return <div>Please enter valid date of birth.</div>;
}
return <div>Invalid field.</div>;
};
We have a component that should show the appropriate message for a certain errorField
. It is a very simple one but by reading it we got some unpleasant feeling. A lot of writing and syntax which is making the code noisy and takes more time to go through. Not to speak about the fact that some tiny details can be missed as well.
By having a deeper look at the code, we can see that after all this is a simple mapping between one value to another. Doesn't this seem like a key/value structure? Here comes the object map, so let's see the refactored example:
import React from 'react';
interface Props {
errorField: 'name' | 'email' | 'password' | 'date';
}
const errorFields = {
name: 'Please enter valid name.',
email: 'Please enter valid email address.',
password: 'Please enter valid password.',
date: 'Please enter valid date of birth.',
default: 'Invalid field.'
};
const ErrorMessage: React.FC<Props> = ({ errorField }) => {
const message = errorFields[errorField] || errorFields.default;
return <div>{message}</div>;
};
Such simple examples are easier to identify and start using object instead of if
/switch
. However, this kind of refactoring/technique is pretty powerful for a lot more complex cases where the benefits are bigger.
More complex example
Let's say we have a button component for connecting our account with Twitter.
import React from "react";
const ConnectTwitterButton: React.FC = () => {
const handleClick = () => {
// Connect Twitter
};
return (
<button onClick={handleClick}>
Connect with <TwitterIcon> Twitter
</button>
);
};
export default ConnectTwitterButton;
This is great, but now imagine we need to extend the current button's functionality to connect with more providers like Twitch/Facebook/.etc and we end up with something like:
import React from 'react';
interface Props {
providerType: 'twitter' | 'twitch' | 'fb';
}
const ConnectAccountButton: React.FC<Props> = ({ providerType }) => {
const getProviderName = () => {
switch (providerType) {
case 'twitter':
return 'Twitter';
case 'twitch':
return 'Twitch';
case 'fb':
return 'Facebook';
default:
return 'Unknown';
}
};
const getProviderIcon = () => {
// return SVG icon
};
const providerName = getProviderName();
const icon = getProviderIcon();
const connectWithTwitter = () => {
// Connect Twitter
};
const connectWithTwitch = () => {
// Connect Twitch
};
const connectWithFacebook = () => {
// Connect Facebook
};
const handleClick = () => {
if (providerType === 'twitter') {
return connectWithTwitter();
}
if (providerType === 'twitch') {
return connectWithTwitch();
}
if (providerType === 'fb') {
return connectWithFacebook();
}
};
return (
<button onClick={handleClick}>
Connect with {icon} {providerName}
</button>
);
};
export default ConnectAccountButton;
We have a couple of things per provider - name, icon and connect function. So what we can do about it?
import React from 'react';
type ProviderType = 'twitter' | 'twitch' | 'fb';
interface Props {
providerType: ProviderType;
}
interface Provider {
icon: React.ReactNode;
name: string;
connect: () => void;
}
const providers: { [key in ProviderType]: Provider } = {
twitter: {
icon: <TwitterIcon />,
name: 'Twitter',
connect: connectWithTwitter
},
twitch: {
icon: <TwitchIcon />,
name: 'Twitch',
connect: connectWithTwitch
},
fb: {
icon: <FacebookIcon />,
name: 'Facebook',
connect: connectWithFacebook
}
};
const ConnectAccountButton: React.FC<Props> = ({ providerType }) => {
const { icon, name, connect } = providers[providerType];
return (
<button onClick={() => connect()}>
Connect with {icon} {name}
</button>
);
};
export default ConnectAccountButton;
The important part is the refactoring itself - providers
object and ConnectAccountButton
component. Now we look at our component it is very easy to understand what is happening and we have the logic centralized in a simple object.
Maybe it would be a bit harder to identify similar cases if you haven't done similar refactoring but ones you have done a couple it becomes easier and more obvious.
Bonus round using array
This one can be helpful with array usage as well. I think the most common example would be a navigation menu where the items are displayed based on some conditions - feature flags, simple checks based on the user data, .etc.
const navigationItems = [
{
path: 'Nav 1',
visible: () => {
// Some visibility logic
}
},
{
path: 'Nav 2',
visible: () => {
// Some visibility logic
}
}
];
// Then we can simply use filter and map to construct our navigation
navigationItems.filter((item) => item.visible()).map((item) => /* The mapped item */ item.path);
Conclusion
We saw a simple and a more complex example of how we can use object map and avoid using conditionals. Hopefully, the code looks much cleaner and easier to understand and extend for everyone.
A pattern can be noticed for cases where we can apply the object map usage - when we have data that is mapped to other data or behavior.
This is a refactoring that is not related specifically to React and JavaScript/TypeScript. It can be used in other languages as well when you see fit.
Of course, this is not a silver bullet and not every if
/switch
statements can be converted to such configuration object.
Thank you for reading this to the end. I hope you enjoyed it and learned something new. If so, you can support me by following me on Twitter where I will share other tips, new articles, and things I learn. If you would like to learn more, have a chat about software development, or give me some feedback, don't be shy and drop me a DM.
Posted on July 26, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
February 4, 2023