Simplifying Code with Maps In JavaScript and React
Updated on · 6 min read|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:
jsxconst 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> ); };
jsxconst 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:
jsxconst 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> ); };
jsxconst 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
, orObject.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.
jsxconst 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> ); };
jsxconst 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.
jsximport 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> ); };
jsximport 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.