Simplifying Code with Maps In JavaScript and React

Updated on · 6 min read
Simplifying Code with Maps In JavaScript and React

In JavaScript, developers often encounter situations where they need to return different results based on various conditions. One such case is when they want to render different JSX inside a component based on some state variable that can be toggled. However, the code can end up being repetitive and hard to maintain, especially when the number of conditions increases. In this blog post, we will explore how to use the Map data structure in JavaScript to simplify and optimize the code. We will start by looking at an example of a data card component and how it can be refactored using Map instead of if/else or switch statements. Finally, we will also discuss the differences between Map and Object, as well as the advantages that Map offers vs Object.

Conditional rendering

In React, when there is a need to return different results based on various conditions, the code can often end up looking like this:

jsx
const DataCard = ({ data }) => { const [cardType, setCardType] = useState("sessions"); const Icon = cardType === "sessions" ? IconSession : IconPost; const title = cardType === "sessions" ? "Daily user sessions" : "Post data"; return ( <div className="data-card"> <Icon /> <Button onClick={() => setCardType((type) => (type === "sessions" ? "post" : "sessions")) } > Switch view </Button> <h2 className="data-card__title">{title}</h2> {data[cardType].map((item) => ( <div className="data-card__data"> <div>{item.name}</div> <div>{item.data}</div> </div> ))} </div> ); };
jsx
const DataCard = ({ data }) => { const [cardType, setCardType] = useState("sessions"); const Icon = cardType === "sessions" ? IconSession : IconPost; const title = cardType === "sessions" ? "Daily user sessions" : "Post data"; return ( <div className="data-card"> <Icon /> <Button onClick={() => setCardType((type) => (type === "sessions" ? "post" : "sessions")) } > Switch view </Button> <h2 className="data-card__title">{title}</h2> {data[cardType].map((item) => ( <div className="data-card__data"> <div>{item.name}</div> <div>{item.data}</div> </div> ))} </div> ); };

In this simple example, we have a data card as part of an analytics dashboard, complete with predefined styles and layout. The card enables users to switch between sessions and post data. The only elements that change are the card icon and title, making it logical to introduce a cardType boolean. This boolean determines the appropriate icon and title to be rendered, as well as the correct data type to be displayed based on the toggle.

Aside from the repetitive nature of the code, there is another issue with this approach. Imagine that our component now needs to display an additional data type, pageViews. In this case, we would first need to refactor the toggle button into a dropdown menu containing the available types. Subsequently, we could introduce a switch statement to replace the verbose if/else conditions. The updated component would then appear as follows:

jsx
const DataCard = ({ data }) => { const [cardType, setCardType] = useState({ value: "sessions", label: "Sessions", }); let Icon, title; switch (cardType.value) { case "sessions": Icon = IconSession; title = "Daily user sessions"; break; case "post": Icon = IconPost; title = "Post data"; break; case "pageViews": Icon = IconPage; title = "Page views"; break; default: throw Error(`Unknown card type: ${cardType}`); } return ( <div className="data-card"> <Icon /> <Dropdown options={[ { value: "sessions", label: "Sessions" }, { value: "post", label: "Posts" }, { value: "pageViews", label: "Page Views" }, ]} onChange={(selected) => setCardType(selected)} /> <h2 className="data-card__title">{title}</h2> {data[cardType.value].map((item) => ( <div className="data-card__data"> <p>{item.name}</p> <p>{item.data}</p> </div> ))} </div> ); };
jsx
const DataCard = ({ data }) => { const [cardType, setCardType] = useState({ value: "sessions", label: "Sessions", }); let Icon, title; switch (cardType.value) { case "sessions": Icon = IconSession; title = "Daily user sessions"; break; case "post": Icon = IconPost; title = "Post data"; break; case "pageViews": Icon = IconPage; title = "Page views"; break; default: throw Error(`Unknown card type: ${cardType}`); } return ( <div className="data-card"> <Icon /> <Dropdown options={[ { value: "sessions", label: "Sessions" }, { value: "post", label: "Posts" }, { value: "pageViews", label: "Page Views" }, ]} onChange={(selected) => setCardType(selected)} /> <h2 className="data-card__title">{title}</h2> {data[cardType.value].map((item) => ( <div className="data-card__data"> <p>{item.name}</p> <p>{item.data}</p> </div> ))} </div> ); };

The code is now significantly less repetitive, and should we need to display additional data types, it's relatively simple to add a new case and option to the dropdown menu. However, there is still room for improvement. What if we could retrieve the title and Icon from a configuration object based on the value of dataType? This suggests the need for a mapping between the data types and component variables, which is where the Map data structure can be utilized.

Map vs Object in JavaScript

In JavaScript, both Map and Object are commonly used to store key-value pairs. However, they have their distinct characteristics, advantages, and use cases. Let's compare their pros and cons to better understand when to choose one over the other.

Map:

  • Pros:
  • Maintains the order of keys based on their insertion, which can be useful when the order of elements is important.
  • Accepts any value as its key, providing greater flexibility.
  • Allows direct iteration, making it easier to work with.
  • Easily determines the size using the size property.
  • Offers performance benefits for frequent addition and removal operations.
  • Cons:
  • Slightly more complex syntax compared to objects.

Object:

  • Pros:
  • Simpler and more familiar syntax for developers.
  • Suitable for simple cases when there is no need to maintain the order of keys.
  • Cons:
  • Keys can only be strings or symbols.
  • Order of keys is not guaranteed.
  • Requires transformations (e.g., using Object.keys, Object.values, or Object.entries) for iteration and determining the size.

In summary, choosing between Map and Object largely depends on the specific requirements of your use case. If you need to maintain the order of keys, work with non-string keys, or benefit from direct iteration and performance advantages, Map is the preferred choice. On the other hand, for simpler cases where the order of keys is not crucial, and you prefer a more familiar syntax, Object can be a suitable option.

Optimising React components with Map

Now that we're familiar with Map, let's refactor our component to take advantage of this data structure.

jsx
const typeMap = new Map([ ["sessions", ["Daily user sessions", IconSession]], ["post", ["Post data", IconPost]], ["pageViews", [" Page views", IconPage]], ]); const DataCard = ({ data }) => { const [cardType, setCardType] = useState({ value: "sessions", label: "Sessions", }); const [title, Icon] = typeMap.get(cardType.value); return ( <div className="data-card"> <Icon /> <Dropdown options={[ { value: "sessions", label: "Sessions" }, { value: "post", label: "Posts" }, { value: "pageViews", label: "Page Views" }, ]} onChange={(selected) => setCardType(selected)} /> <h2 className="data-card__title">{title}</h2> {data[cardType.value].map((item) => ( <div className="data-card__data"> <p>{item.name}</p> <p>{item.data}</p> </div> ))} </div> ); };
jsx
const typeMap = new Map([ ["sessions", ["Daily user sessions", IconSession]], ["post", ["Post data", IconPost]], ["pageViews", [" Page views", IconPage]], ]); const DataCard = ({ data }) => { const [cardType, setCardType] = useState({ value: "sessions", label: "Sessions", }); const [title, Icon] = typeMap.get(cardType.value); return ( <div className="data-card"> <Icon /> <Dropdown options={[ { value: "sessions", label: "Sessions" }, { value: "post", label: "Posts" }, { value: "pageViews", label: "Page Views" }, ]} onChange={(selected) => setCardType(selected)} /> <h2 className="data-card__title">{title}</h2> {data[cardType.value].map((item) => ( <div className="data-card__data"> <p>{item.name}</p> <p>{item.data}</p> </div> ))} </div> ); };

Notice how much leaner the component has become after refactoring the switch statement into a Map. At first, the Map might seem a bit unusual, resembling a multidimensional array. The first element is the key, and the second one is the value. Since keys and values can be anything, we map our data types to arrays, where the first element is the title, and the second one is the icon component. Normally, extracting these two values from this nested array would be somewhat complicated, but the destructuring assignment syntax makes it a simple task. An additional benefit of this syntax is that we can name our variables anything, which is handy in case we want to rename the title or Icon without modifying the Map itself. The Map is declared outside the component, so it doesn't get unnecessarily re-created on every render.

While the example above may not be convincing enough for developers to use Map over Object, as they are more familiar with the Object syntax, consider a scenario where we have a UserProfile component that displays the user's information based on the selected tab. Here, using a Map is more appropriate as it allows us to maintain the order of the tabs and makes it easier to add or remove tabs in the future.

jsx
import React, { useState } from "react"; const UserInfo = ({ user }) => <div>{user.name}</div>; const UserPosts = ({ user }) => <div>{user.posts.length} posts</div>; const UserSettings = () => <div>Settings</div>; const tabMap = new Map([ ["info", ["Info", UserInfo]], ["posts", ["Posts", UserPosts]], ["settings", ["Settings", UserSettings]], ]); const UserProfile = ({ user }) => { const [selectedTab, setSelectedTab] = useState("info"); return ( <div> <div className="tabs"> {[...tabMap].map(([key, [label]]) => ( <button key={key} className={selectedTab === key ? "active" : ""} onClick={() => setSelectedTab(key)} > {label} </button> ))} </div> <div className="tab-content"> {React.createElement(tabMap.get(selectedTab).Component, { user })} </div> </div> ); };
jsx
import React, { useState } from "react"; const UserInfo = ({ user }) => <div>{user.name}</div>; const UserPosts = ({ user }) => <div>{user.posts.length} posts</div>; const UserSettings = () => <div>Settings</div>; const tabMap = new Map([ ["info", ["Info", UserInfo]], ["posts", ["Posts", UserPosts]], ["settings", ["Settings", UserSettings]], ]); const UserProfile = ({ user }) => { const [selectedTab, setSelectedTab] = useState("info"); return ( <div> <div className="tabs"> {[...tabMap].map(([key, [label]]) => ( <button key={key} className={selectedTab === key ? "active" : ""} onClick={() => setSelectedTab(key)} > {label} </button> ))} </div> <div className="tab-content"> {React.createElement(tabMap.get(selectedTab).Component, { user })} </div> </div> ); };

In this example, we have a tabMap that maps the tab keys to their respective labels and components. The order of the tabs is maintained, and we can easily iterate through the map to render the tab buttons. Additionally, the selectedTab state variable stores the currently active tab, and we use React.createElement to render the appropriate component for the selected tab and pass it the user prop.

Since Map does not have a map method, it needs to be transformed into an array first. This can be done by using array spread or Array.from. Here again, we benefit from the destructuring assignment, so we can easily access key and label inside the map method's callback.

Using a Map in this scenario is more advantageous than using an Object, as it simplifies the code, maintains the order of the tabs, and allows us to leverage the direct iteration and other benefits that Map provides.

For those who are curious about other use cases of Map in JavaScript, you may find this post interesting: Removing Duplicates with Map In JavaScript.

Conclusion

In JavaScript, developers often encounter situations where they need to return different results based on various conditions. One such case is when they want to render different JSX inside a component based on some state variable that can be toggled. However, the code can end up being repetitive and hard to maintain, especially when the number of conditions increases. In this blog post, we explored how to use the Map data structure in JavaScript to simplify and optimize the code. We started by looking at an example of a data card component and how it can be refactored using Map instead of if/else or switch statements. We also discussed the differences between Map and Object, as well as the advantages that Map offers compared to Object.

By leveraging the Map data structure, we can create more efficient and maintainable React components. When faced with complex use cases, the Map can be an invaluable tool for maintaining order, iterating directly, and enhancing performance. As demonstrated in our examples, using Map can lead to leaner and more manageable code, especially when dealing with components that have a dynamic number of conditions. By understanding the differences between Map and Object, and knowing when to use one over the other, we can make more informed decisions and write cleaner, more efficient code in our projects.

References and resources