Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
November 18, 2022 12:42 pm GMT

Data Structures In Frontend JavaScript In The Real World (With React Code Examples)

Data structures can be intimidating. Especially for the self-taught folks among us. If you already tried to study data structures in JavaScript you know what typically happens:

  1. A bunch of CS theory is thrown at you.
  2. You read pages of code that manually implement e.g. a linked list.
  3. All the examples are abstract using foo and bar or cats and dogs.

On top of that, youre confronted with smart-looking diagrams like this:

Confusing diagram of a tree data structure.

But if youve been around for a while you may say Ive never seen this in my frontend projects! And youre right. I havent either.

At the same time, you certainly use some data structures already. Not as obvious as new HashMap() but disguised as simple objects or arrays. And if you dont, this is a good opportunity to get started.

On this page, well

  • answer the question if you should study data structures and when they can be helpful.
  • look at a few common data structures that you can find in real-world frontend projects.

No worries, you wont find full-blown implementations of any data structure here. There are plenty of resources that cover this. But you will see examples that you might recognize from your own code or that can use in your projects or on the job.

Table Of Contents

  1. Do you have to study data structures as a frontend JavaScript developer?
  2. Map
    1. Description
    2. Real-World Example: Messages with user names
  3. Set
    1. Description
    2. Real-World Example: Keeping track of selected items
  4. Stack
    1. Description
    2. Real-World Example: Undo previous actions
  5. Queue
    1. Description
    2. Real-World Example: Notifications
  6. Tree
    1. Description
    2. Real-World Example: Nested menus or comments.

Do you have to study data structures as a frontend JavaScript developer?

This question is likely to trigger a debate. Here are the two extremes:

  • Yes. Data structures are essential for any programmer no matter what your tech stack is.
  • To me all that data structure stuff is theory crap and in my 13+ yrs as a web developer, I have never really seen a case where this stuff would be needed.

For a lot of devs (especially the self-taught among us), the second answer is much more comforting. And its true. At least partly. Here are a few reasons why you wont find many advanced or formal data structures in JavaScript frontends:

  • Data sets on the frontend typically arent large enough to see the benefits of advanced data structures.
  • JavaScript doesnt even support many data structures natively.
  • React (as the most widely spread frontend framework) doesnt work well with custom data structures since it requires immutable state updates. Cloning a custom data structure would likely eat up any performance benefits.

But as I said, the you dont need data structures on the frontend answer is only partly true.

  1. You might need to know data structures for technical interviews (emphasis on might).
  2. More importantly, a common problem among Junior frontend devs that leads to really messy code is picking the wrong data structure. Here is an example.

Selecting a data structure can be the make-or-break decision of your codes quality. It decides what the rendering logic looks like or how you update the data. So it really can be helpful to know a few options.

Map

Description

A (hash) map, hash table, or dictionary is basically a key-value storage. In JavaScript we actually use this all the time:

{  key1: "value 1",  key2: "value 2",  key3: "value 3",}

We also have a native JavaScript alternative in form of a Map. It has a few handy methods and properties (such as Map.size), is optimized performance-wise for accessing values by key, and allows any kind of type as keys (even objects).

Another advantage of the Map: with objects the keys are converted to strings as shown below. A Map on the other hand preserves the original type.

const numericKeys = {  1: "value-1",  2: "value-2",}console.log(Object.keys(numericKeys))// ["1", "2"]

Real-World Example: Messages with user names

In the wild, you can find maps when two data sets reference each other. As in this chat component.

We have two arrays (messages and users). Each message has a reference to the author via its userId field.

const messages = [  {    id: "message-1",    text: "Hey folks!",    userId: "user-1"  },  {    id: "message-2",    text: "Hi",    userId: "user-2"  },  ...];const users = [  {    id: "user-1",    name: "Paul"  },  {    id: "user-2",    name: "Lisa"  },  ...];

We could find the corresponding user when we render each message.

messages.map(({ id, text, userId }) => {  const name = users.find((user) => user.id === userId).name;  return ... // return JSX}

But thats not ideal performance-wise as we have to iterate over the users array for each message. Instead, we can use a map.

Lets start with a simple key-value object. The goal is to create an object like this:

{  "user-1": "Paul",  "user-2": "Lisa",  ...}

We can achieve this with the Array.reduce() function (find the complete code here).

const messages = [  {    id: "message-1",    text: "Hey folks!",    userId: "user-1"  },  ...];const users = [  {    id: "user-1",    name: "Paul"  },  ...];function ChatUsingMap() {  const namesById = users.reduce(    (prev, user) => ({ ...prev, [user.id]: user.name }),    {}  );  return messages.map(({ id, text, userId }) => (    <div key={id}>      <div>{text}</div>      <div>{namesById[userId]}</div>    </div>  ));}

Alternatively, we can use a real Map. The constructor accepts an array of key-value pairs, so we can get rid of the reduce function (here is the code on CodeSandbox).

function ChatUsingMap() {  const namesById = new Map(users.map(({ id, name }) => [id, name]));  return messages.map(({ id, text, userId }) => (    <div key={id}>      <div>{text}</div>      <div>{namesById.get(userId)}</div>    </div>  ));}

For clarity: we pass an array of arrays like this to the Map constructor:

[  ["user-1", "Paul"],  ["user-2", "Lisa"],  ...]

React on the job - email course

Set

Description

A Set is a keyed collection as well that is natively supported in JavaScript. But while its easier to think of a Map as an object, a Set is more like an array. Its important to note that each value in a Set is unique. You cant add a value twice. Thats why you may see a Set used to remove duplicates from an array.

const uniqueValues = [...new Set([1, 1, 2, 3, 3])];// [1, 2, 3]

Compared to an array, the main advantage is that you can check if a Set contains a value in a performant way.

Real-World Example: Keeping track of selected items

In the wild, you can find a set when tracking items selected by a user. Like in this table:

Example of using a map

When the user selects an item we keep track of it by adding its id to a set. When the user unselects we remove it from the set again (here is the code on CodeSandbox).

import { useState } from "react";const rows = [  {    id: "id-1",    name: "Row 1",  },  {    id: "id-2",    name: "Row 2",  },  ...];function TableUsingSet() {  const [selectedIds, setSelectedIds] = useState(new Set());  const handleOnChange = (id) => {    const updatedIdToSelected = new Set(selectedIds);    if (updatedIdToSelected.has(id)) {      updatedIdToSelected.delete(id);    } else {      updatedIdToSelected.add(id);    }    setSelectedIds(updatedIdToSelected);  };  return (    <table>      <tbody>        {rows.map(({ id, name }) => (          <tr key={id}>            <td>              <input                type="checkbox"                checked={selectedIds.has(id)}                onChange={() => handleOnChange(id)}              />            </td>            <td>{id}</td>            <td>{name}</td>          </tr>        ))}      </tbody>    </table>  );}

A final note: using a Set can be a great alternative to using an array. A problematic implementation of the select items functionality that you can often see looks something like this:

const [selectedRows, setSelectedRows] = useState(  rows.map((row) => ({ selected: false })));const handleOnChange = (index) => {  const updatedSelectedRows = [...selectedRows];  updatedSelectedRows[index] = !updatedSelectedRows[index];  setSelectedRows(updatedSelectedRows);};

This might look straightforward at first. But accessing the elements via the index can cause problems. Just one example: what happens if the rows can be sorted in a different order? Wed have to keep the selectedRows array in sync and sort it the same way.

If you want to see how migrating to a Set can lead to much cleaner and reduced code check this refactoring session.

Stack

Description

A basic stack has two features:

  • You can add an item to the top of the stack.
  • You can remove an item from the top of the stack.

This is called Last in, first out (aka LIFO). Might sound complicated but we can implement it with a simple array (as its not natively supported in JavaScript).

const stack = [];// add item to the "top"stack.push("value");// remove item from the "top"const topItem = stack.pop();

Real-World Example: Undo previous actions

In the wild, you can find a stack when you need to keep track of the history of user interactions to build an undo functionality. Like in this table where the user can remove each row.

Example of undo functionality using a stack

Whenever a user deletes a row from the table we add it to a history array (our stack). When the user wants to undo the deletion we get the latest row from the history and re-add it to the table. Some of the functions arent shown below but you can find the complete code here on CodeSandbox.

Note that we cant just use Array.push() and Array.pop() as React requires immutable data. Instead, we use Array.concat() and Array.slice() because both return new arrays.

import { useReducer } from "react";const rows = [  {    id: "id-1",    name: "Row 1",  },  {    id: "id-2",    name: "Row 2",  },  ...];// "history" is our stackconst initialState = { rows, history: [] };function reducer(state, action) {  switch (action.type) {    case "remove":      return {        rows: removeRow(state, action),        // Array.concat() as immutable alternative to Array.push()        history: state.history.concat({          action,          row: state.rows[action.index]        })      };    case "undo":      return {        rows: addRowAtOriginalIndex(state),        // Array.slice() as immutable alternative to Array.pope()        history: state.history.slice(0, -1)      };    default:      throw new Error();  }}function TableUsingStack() {  const [state, dispatch] = useReducer(reducer, initialState);  return (    <>      <button        onClick={() => dispatch({ type: "undo" })}        disabled={state.history.length === 0}      >        Undo Last Action      </button>      <table>        <thead>          <tr>            <th>ID</th>            <th>Name</th>            <th></th>          </tr>        </thead>        <tbody>          {state.rows.map(({ id, name }, index) => (            <tr key={id}>              <td>{id}</td>              <td>{name}</td>              <td>                <button onClick={() => dispatch({ type: "remove", id, index })}>                  Delete                </button>              </td>            </tr>          ))}        </tbody>      </table>    </>  );}

As mentioned in the comments, history is our stack. When a user clicks one of the Remove buttons we add the action plus some additional data to the history. The Undo Last Action button then removes the latest action from the history stack and adds the row again to the data.

React on the job - email course

Queue

Description

A queue is very similar to a stack. But instead of removing the element that was added last, we remove the element that was first added to the queue. This is called First in, first out (aka FIFO).

Like a queue in the supermarket.

We can implement it again using a simple array as it's not natively supported in JavaScript.

const queueu = [];// add item to the "end"queueu.push("value");// remove item from the "beginning"const firstItem = queueu.shift();

Real-World Example: Notifications

In the wild, you can find a queue when you have show notifications on top of each other. You know, the kind of notification that pops up at the bottom of the screen and disappears after a while.

In this case, a notification pops up whenever a user joins our imaginary community.

Example of notification queue

Whenever we click the button a message is added to the notifications array (our queue). Additionally, a timeout is set that removes the first notification in the queue again. You can find the complete code here on CodeSandbox.

import { faker } from "@faker-js/faker";import { useState } from "react";function ButtonAddingNotifications() {  const [notifications, setNotifications] = useState([]);  const addNotification = () => {    setNotifications((previous) => {      // use Array.concat() as immutable alternative to Array.push()      return previous.concat(`${faker.name.firstName()} joined!`);    });    setTimeout(() => {      // use Array.slice() as immutable alternative to Array.shift()      setNotifications((previous) => previous.slice(1));    }, 1000);  };  return (    <>      <button onClick={() => addNotification()}>        Invite User To Community      </button>      <aside>        {notifications.map((message, index) => (          <p key={index}>{message}</p>        ))}      </aside>    </>  );}

Tree

Description

A tree is a nested data structure. It starts with a parent that has children. The children again can have children of their own and so on. Together with trees you often find recursive functions.

Here is one example using a nested object:

{  name: "Parent",  children: [    {      name: "Child 1",    },    {      name: "Child 2",      children: [        {          name: "Grandchild 21",        }      ]    }  ]}

There are many different ways of defining a tree. As an alternative to the above structure, we could also use a flat array. In that case, all elements need an ID and reference each other.

[  {    id: "parent-1",    name: "Parent",    // references to children via their IDs    children: ["child-1", "child-2"]  },  {    id: "child-1",    // reference to the parent vid its ID    name: "Child 1",    parent: "parent-1"  },  {    id: "child-2",    name: "Child 2",    parent: "parent-1",    children: ["grandchild-2"]  },  {    id: "grandchild-21",    name: "Grandchild 21",    parent: "child-2"  }]

The weird thing from a human perspective is that each child can only have one parent. Thats why you can also often find the terms node (aka any of the items), child, and edge (the connection between two items).

Real-World Example: Nested menus or comments.

As mentioned, trees are nested data structures. And you can find those when you have nested menus that are defined on the backend (e.g. in a CMS).

Nested menu tree

Another common example is nested comments as you can find them on many blogs or Reddit. This component recursively renders a nested tree structure consisting (the complete code on CodeSandbox).

const menuItems = [  {    text: "Menu 1",    children: [      {        text: "Menu 1 1",        href: "#11",      },      {        text: "Menu 1 2",        href: "#12",      },    ],  },  {    text: "Menu 2",    href: "#2",  },  {    text: "Menu 3",    children: [      {        text: "Menu 3 1",        children: [          {            id: "311",            text: "Menu 3 1 1",            href: "#311",          },        ],      },    ],  },];// Menu and MenuItem are recursively calling each otherfunction Menu({ items }) {  return (    <ul>      {items.map((item, index) => (        <MenuItem key={index} {...item} />      ))}    </ul>  );}function MenuItem({ text, href, children }) {  // the break condition:   // stop recursion if the item doesn't have children  if (!children) {    return (      <li>        <a href={href}>{text}</a>      </li>    );  }  // recursively create a submenu  return (    <li>      {text}      <Menu items={children} />    </li>  );}// the root componentfunction NestedMenu() {  return <Menu items={menuItems} />;}

The nested tree structure above is quite easy to read for humans and also simple to render. But if we need to update the data we quickly run into problems. Especially when we need to change the data immutably (as we have to with React state).

In this case, a great alternative is a flat array instead of the nested objects above. This is a bit heavier on the frontend logic as we have to resolve the ID references. But its very easy to interact with and probably also easier to handle for the backend.

const menuItems = [  {    id: "1",    text: "Menu 1",    children: ["11", "12"],    isRoot: true,  },  {    id: "11",    text: "Menu 1 1",    href: "#11",  },  {    id: "12",    text: "Menu 1 2",    href: "#12",  },  {    id: "2",    text: "Menu 2",    href: "#2",    isRoot: true,  },  {    id: "3",    text: "Menu 3",    children: ["31"],    isRoot: true,  },  {    id: "31",    text: "Menu 3 1",    children: ["311"],  },  {    id: "311",    text: "Menu 3 1 1",    href: "#311",  },];function Menu({ itemIds, itemsById }) {  return (    <ul>      {itemIds.map((id) => (        <MenuItem key={id} itemId={id} itemsById={itemsById} />      ))}    </ul>  );}function MenuItem({ itemId, itemsById }) {  const item = itemsById[itemId];  if (!item.children) {    return (      <li>        <a href={item.href}>{item.text}</a>      </li>    );  }  return (    <li>      {item.text}      <Menu itemIds={item.children} itemsById={itemsById} />    </li>  );}function NestedMenu() {  const itemsById = menuItems.reduce(    (prev, item) => ({ ...prev, [item.id]: item }),    {}  );  const rootIds = menuItems.filter(({ isRoot }) => isRoot).map(({ id }) => id);  return <Menu itemIds={rootIds} itemsById={itemsById} />;}

Note that recursive functions are very elegant but for very deeply nested trees you might exceed the JavaScript engines call stack size. Here is an example that throws an error at a recursion depth of 10962:

Example of a recursive function exceeding the maximum call stack size

In our case, we wont run into this problem as we wont nest thousands of menu items.

React on the job - email course


Original Link: https://dev.to/jkettmann/data-structures-in-frontend-javascript-in-the-real-world-with-react-code-examples-3506

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To