JS and everyday data structures

Let’s start with a quote that once changed my life. I am sure that this quote and its understanding will also change yours if you have not heard it yet. Bad programmers worry about the code. Good programmers worry about data structures and their relationships. [Linus Torvalds] We will talk about data structures, but first, it […]


Let’s start with a quote that once changed my life. I am sure that this
quote and its understanding will also change yours if you have not heard it
yet.

Bad programmers worry about the code. Good programmers worry about data
structures and their relationships.
 [Linus Torvalds]

We will talk about data structures, but first, it is impossible to talk
about them without mentioning algorithms. 

Algorithms are the steps we need to take to solve a problem. Data
structures are organized data with efficient and convenient access to
them.

Programming is always = algorithms ➕ data structures.

When solving a problem, it should be kept in mind that choosing the right
data structure may be a solution for the problem (no need to write any
code)

Data structures in JS

JS has two basic data-structures that can be reused in different ways
depending on the requirements. Therefore, they support a large number of
methods of different structures which would be separate in other program
languages.

  • Object (Enum, Map, Graph, etc);
  • Array (List, Stack, Queue, etc).

Let’s get started!

We will use this user type for further examples:

type User = {
 name: string;
 gender: string;
}

Do not worry if you are not familiar with TypeScript. The example of the type
is given so that you understand the approximate structure of the user.

The task

Let’s imagine that we have a task — we need to display a list of users using
the appropriate emoji by the user’s gender. For now we have only two options —
male and female.

It might look like this:

<ul>
    {users.map((it) => (
    <li>
        <h3>{it.name</h3>
        <p>{it.gender === 'male' ? '👨' : '👩</p>
    </li>
    ))}
</ul>

That’s it. The task is done. Buuut… we can do it better.

Let’s imagine that in our code there will be or there already are places where
we check something by the user’s gender. We duplicate the code. It is not
good. Also if we use *just a string* all the time, sooner or later, we will
make a mistake. In fact, such errors are not very easy to find.

Let’s solve this task too. We will protect ourselves from this. We will use a
single source of truth — CONSTANT’s.

Constant

CONSTANT s are used to describe data that is known *before the start* of the
program and that *should not be changed* during the execution of the program.

When associated with an identifier, a constant is said to be “named,”
although the terms “constant” and “named constant” are often used
interchangeably.  [Wikipedia]

The constant can be declared with any keyword for variables (var, let or
const), but const is commonly used.

Constants are a very important and popular approach to organize program code.
And there are several conventions among developers for them:

  • Constant must be declared at the top of the program/module (after imports, if any);
  • Constant must have a name in capital letters;
  • Constant must not be redefined anywhere in the program.

Examples

// bad
// src/common/constants/earth/index.js
const earthRadius = 6371;

// src/components/common/user-item/user-item.jsx
const UserItem = ({ user }) => {
  const USER_ROLE = getUserRole(user);
  return <li>{user.name}, {USER_ROLE}</li>
}

// src/components/dashboard/dashboard.jsx
let USER_TYPE = 'user';
if (checkIsAdmin(user)) {
  USER_TYPE = 'admin';
}

// good
// src/common/constants/earth/index.js
const EARTH_RADIUS = 6371;

// src/components/common/user-item/user-item.jsx
const UserItem = ({ user }) => {
  const userRole = getUserRole(user);
  return <li>{user.name}, {userRole}</li>
}

// src/components/dashboard/dashboard.jsx
let userType = 'user';

if (checkIsAdmin(user)) {
  userType = 'admin';
}

Here is an improved solution for our task:

// src/common/constants/user/index.js
const MALE_GENDER_TYPE = 'male';
const FEMALE_GENDER_TYPE = 'female';

// src/components/common/users-list/users-list.jsx
<ul>

 {users.map((it) => (
   <li>
     <h3>{it.name}</h3>
     <p>{it.gender === MALE_GENDER_TYPE ? '👨' : '👩'}</p>
   </li>
 ))}

</ul>

Much better, but we can improve it even more.

Have you noticed that we duplicate
GENDER_TYPE
in the name? It is not critical, but annoying. Also if we needed some
values, we would have to import each constant separately.

It would be cool if there was a data structure that would help us with this
as well. And there is such a structure —
Enum.

Enum

Enum (enumeration) — a data
structure that is used to enumerate a set of fixed values (set of
constants).

Other programming languages have a separate data type for Enum. But JS does
not have this type of data (at least for now, more on that below). The
regular object is usually used to imitate Enum in JS.

For this structure, JavaScript also has conventions among developers:

  • Enum must start with a capital letter;
  • Enum must be singular;
  • Enum must have uppercase keys;
  • Enum must not be changed anywhere in the program.

 Examples:

// bad// src/common/enum/user/user-role-enum.enum.js
const userRoleEnum = {
    user: 1,
    viewer: 2,
};

// or
const userRoles = {
    user: 1,
    viewer: 2,
};

// or
const UserRoles = {
    user: 1,
    viewer: 2,
}
if (checkHasAdminRole()) {
    UserRoles.admin = 3
}

// good
// src/common/enum/user/user-role.enum.js
const UserRole = {
    USER: 1,
    VIEWER: 2,
    ADMIN: 3
};

This is how the solution using the
Enum might look like:

// src/common/enums/user/gender-type.enum.js
const GenderType = {
    MALE: 'male',
    FEMALE: 'female'
};

// src/components/common/users-list/users-list.jsx
<ul>
    {users.map((it) => (
      <li>
        <h3>{it.name}</h3>
        <p>{it.gender === GenderType.MALE ? '👨' : '👩'}</p>
      </li>
    ))}
</ul>

We could keep using constants but it is much better and more correct to use
structures that are better suited for this.

Also if we do the task with TypeScript, we can also change the type of user
a little bit. This is
many times better than using just strings.

type User = {
    name: string;
    gender: typeof GenderType[keyof typeof GenderType];
}

Let’s now imagine that a business comes to us and says that they want us to
add a new type for the user’s gender — ‘non-binary’. We could do something
like this:

// src/components/common/users-list/users-list.jsx
<ul>
    {users.map((it) => (
      <li>
        <h3>{it.name}</h3>
          <p>
            {it.gender === GenderType.MALE
              ? '👨'
              : it.gender === GenderType.FEMALE
              ? '👩'
              : '🧑'}
          </p>
      </li>
    ))}
</ul>

But it looks very ugly. We could replace this with a
switch statement, or a helper
function that does it itself. This is a significant improvement, but we can
also use special structures for this. We can use a
Map data structure to make the solution more flexible and readable.

Map

Map (dictionary, associative
array, map) — a data structure that is used to map one value to
another.

JS has a new Map constructor out of the box. The key difference from a common object is the ability to
use any data type (even an object) as a key.

Usually, using the JS Map constructor is overkill. If you need the
functionality that the JS Map provides, you have to use it. But usually a
common object is used to imitate this structure.

There are some conventions how to configure this structure:


  • Maps must have a name by one of these patterns —
    xToY or
    xMap (userToPerson
    or userMap). The first
    pattern is used more often.

 Examples:

// bad
// src/common/maps/user/user-roles.map.js
const userRoles = {
    user: 'Default User',
    viewer: 'Checker',
    admin: 'Administrator'
};

// good
// src/common/maps/user/user-role-to-readable.map.js
const userRoleToReadable = {
    user: 'Default User',
    viewer: 'Checker',
    admin: 'Administrator'
};

// src/common/maps/user/user-role-map.map.js
const userRoleMap = {
    user: 'Default User',
    viewer: 'Checker',
    admin: 'Administrator'
};

Here is how the solution might look like using the
Map:

// src/common/enums/gender-type.enum.js
const GenderType = {
    MALE: 'male',
    FEMALE: 'female',
    NON_BINARY: 'non-binary'
};

// src/common/maps/gender-type-to-emoji.map.js
const genderTypeToEmoji = {
    [GenderType.MALE]: '👨',
    [GenderType.FEMALE]: '👩',
    [GenderType.NON_BINARY]: '🧑'
};

// src/components/common/users-list/users-list.jsx
<ul>
     {users.map((it) => (
       <li>
         <h3>{it.name}</h3>
         <p>{genderTypeToEmoji[it.gender]}</p>
       </li>
     ))}
</ul>

Have you noticed how we reused all the structures we have just learned
about?

The choice of suitable data structures saved us from writing additional
code. Using data structures and their combinations is a very
powerful tool that is very much appreciated among developers.

Here are some more examples where using data structures helps a lot:

Select Control:

// src/common/maps/gender-type-to-readable.map.js
const genderTypeToReadable = {
    [GenderType.MALE]: 'Male',
    [GenderType.FEMALE]: 'Female',
    [GenderType.NON_BINARY]: 'Non Binary (NB)'
};

// src/components/sign-up/components/register-form/register-form.jsx
const genderOptions = getOptions(Object.values(GenderType), (gender) => ({
    label: genderTypeToReadable[gender],
    value: gender
}))

<Select label="Gender:" options={genderOptions}/>

 Tabs:

// src/components/dashboard/common/enums/tab-name.enum.js
const TabName = {
    USERS: 'Users',
    FORM: 'Register Form',
    PERMISSIONS: 'User Permissions'
};

// src/components/dashboard/dashboard.jsx
const tabOptions = getOptions(Object.keys(TabName));
const Dashboard = () => {
    const [currentTab, setCurrentTab] = React.useState(TabName.USERS);
    const getScreen = (tabName) => {
        switch (tabName) {
            case TabName.USERS: {
                return <User/>;
            }
            case TabName.FORM: {
                return <RegisterForm/>;
            }
            case TabName.PERMISSIONS: {
                return <Permissions/>;
            }
        }

        return null;
    };

    return (
    <>

    <TabList
        options={tabOptions} onChange={setCurrentTab}/>

        <div
        className="screen-wrapper">{getScreen(currentTab)}</div>
    </>
    );
};

 getFilteredOffers:

// src/components/dashboard/common/maps/user-offer-to-validation-cb.map.js
const userOfferToValidationCb = {
    checkHasCurrentType() { /* checking... */ },
    checkHasCurrentPrice() { /* checking... */ },
    checkHasCurrentRooms() { /* checking... */ }
};

// src/components/dashboard/helpers/get-filtered-offers/get-filtered-offers.helper.js
const getFilteredOffers = (offers) => {
    return offers.filter((offer) => {
        const isSuitable = Object.keys(userOfferToValidationCb).every((key) => {
            const validationFn = userOfferToValidationCb[key];

            return validationFn(offer);
        });

        return isSuitable;
    });
};

Here is an example of the
getOptions helper:

 Example:

// src/helpers/options/get-default-option/get-default-option.helper.js
const getDefaultOption = (value) => ({
    value,
    label: value
});

// src/helpers/options/get-options/get-options.helper.js
const getOptions = (values, cb = getDefaultOption) => values.map(cb);

TypeScript Enum

TypeScript has a keyword for the Enum’s. The key difference from an
object-enum is that with the TypeScript enum we can read values in two
ways.

// src/common/enums/gender-type.enum.ts
enum GenderType {
     MALE = 'male',
     FEMALE = 'female',
     NON_BINARY = 'non-binary'
};
console.log(GenderType.MALE) // male
console.log(GenderType['male']) // MALE

Cool! But I have almost never seen this used in production code. Therefore,
I use and recommend that you use Enum in TypeScript in this way:

// src/common/enums/gender-type.enum.ts
const GenderType = {
     MALE: 'male',
     FEMALE: 'female',
     NON_BINARY: 'non-binary'
} as const;

With const assertions we still use a regular object, but now it’s on steroids.

Moreover, enum is always a reserved word in JavaScript, which means that maybe someday we will have a construct
that the language itself will offer us.

Also, programs that compress the JS code cannot compress TS
enum s. The same cannot be
said about the objects that pure JavaScript offers us.

Enum TC39 proposals

As I said above, that Enum may appear in vanilla JavaScript someday. You
can view the proposals that we have at the moment:

  • https://github.com/rwaldron/proposal-enum-definitions
  • https://github.com/rbuckton/proposal-enum
  • https://es.discourse.group/t/enumerations-syntactic-sugar/144

Usually, the solution that the language itself can offer is many times more
effective than any third-party library can offer.

Do not forget to check the proposals that may appear in JavaScript language, and vote for the most interesting
for you.

Conclusions

Data structures are awesome! They help us solve tasks in a neat and
convenient way. With the right data structures, it’s easier to build
algorithms or not write code at all. The data structure may already be the
solution to the task.

A huge plus is that if you do them according to the conventions that are
present in the JavaScript language, most developers will understand your
code much faster than coming up with something ‘new.’

Most things are already invented for us!

Do not forget to put the constants of the same type into
Enum s, then map them into
the format you need, and enjoy the beauty of the data structures.

The post JS and everyday data structures appeared first on Offshore Custom Software Development Company | Binary Studio, Ukraine.

Source: Binary Studio