loading...
Cover image for How to keep cleaner React components with object map?

How to keep cleaner React components with object map?

martinbelev profile image Martin Belev Originally published at belev.dev Updated on ・4 min read

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.

Discussion

pic
Editor guide
Collapse
monfernape profile image
Usman Khalil

This is quite a neat pattern.

Collapse
martinbelev profile image
Martin Belev Author

Yep, I really like it and use it very often. However, I don't know if it has a name and is really a known pattern 🤔

Collapse
konradlinkowski profile image
Konrad Linkowski

This rule applies not only to React. It's a JavaScript basic coding pattern.
React is starting to be a new jQuery and I think it's not good at all.

Collapse
martinbelev profile image
Martin Belev Author

Yep, it is not related only to JavaScript in general. It can be used in most programming languages.

Can you elaborate on "React is starting to be a new jQuery and I think it's not good at all."? You mean the pattern is not good in your opinion or just React being the new jQuery, if the later how so?

Every technology can be wrongly used and we can end up in code that is hell to manage. But using React reasonably, I don't see similarities with jQuery to be honest.