Should mutators be encapsulated within a class contained in a JS Module for better code organization and maintainability?

In order to maximize functionality of our new product using JavaScript, we have implemented an Authentication module that manages a tokenPromise which is updated upon user logins or token refreshes. It seems imperative to allow for mutation in this process.

Rather than placing the tokenPromise at the module level, I opted to create a class solely dedicated to high-level functions that restrict how the state can be mutated. Other helper functions, which are pure and do not require state mutation, remain outside the class. This strategy greatly aids in understanding when the member might undergo changes as it is closely associated with all operations capable of changing it.

I have yet to come across similar patterns - is this approach considered best practice, or should we explore other options? Below is the class containing the mutable data, which is exported from Authentication.ts.

export default class Authentication {
  public static async getAuthToken(): Promise<string> {
    if (!this.tokenPromise || await hasExpired(this.tokenPromise)) {
      // Either we've never fetched, memory was cleared, or token expired
      this.tokenPromise = getUpdatedTokenPromise();
    }

    return (await this.tokenPromise).idToken;
  }

  public static async logOut(): Promise<void> {
    this.tokenPromise = null;
    await LocalStorage.clearAuthCredentials();

    // Simply restart to log out for now
    RNRestart.Restart();
  }

  private static tokenPromise: Promise<IAuthToken> | null;
}

// Afterwards, at the module level, we define all helper functions that do not require mutating this module's state - such as getUpdatedAuthToken(), etc.

An underlying principle appears to be: maintain objects with mutable state as concise as possible, exposing only high-level compact methods for state mutation (e.g. logOut and refreshAuthToken, rather than get/set authToken).

Answer №1

In my approach, I have developed a class that specifically contains high-level functions to control how the state is modified. This method has proven effective in determining when changes to the member might occur, as all operations that could alter it are located together.

Adhering to a standard practice in object-oriented programming, known as separation of concerns, the encapsulation of state changes within the object prevents any external code from directly mutating it.

I also keep other helper functions that are pure or do not require state mutation outside the class.

While I agree with this to some extent, it is important to include helper functions (methods) relevant to instances within the class or in close proximity to it. Placing them in the same module may suffice, especially if they access private parts of the objects. To differentiate between pure and impure functions, using naming conventions like adding "get" prefixes for pure methods can be helpful.

Another approach is to offer a separate immutable interface for the class containing only pure methods, achieved through a secondary class declaration with a method for conversion between the two representations.

Limiting exposure to high-level concise methods for state mutation

Although restricting access to compact mutational methods is essential, it's worth noting that there will still be implicit ways to access the state (utilized by pure helper functions). It may be beneficial to make these pathways explicit as well.

When managing mutable state, the sequence of writes and reads is crucial not just internally but externally too (when the overall object state changes). Establishing a convention such as "properties (and getters) are pure, methods might be impure" can aid significantly in maintaining clarity.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the reason for Cypress choosing to omit specific commands?

The test below aims to scan and authenticate a QR code, then utilize the received authentication token. However, for some reason, the last two commands (.type) are not being executed. I've been stuck at this point for quite some time now. Any insights ...

Angular (4, 5, 6, 7) - An easy guide to implementing slide in and out animations using ngIf

How can you implement a basic sliding animation in Angular4 to show and hide a container element? For example: <div *ngIf="show"> <!-- Content --> </div> Slide the content in (similar to jQuery's slideDown() method) from top t ...

The Expo TypeScript template highlights JSX errors such as "Cannot assign type 'boolean' to type 'View'. TypeScript error 2322 at line 5:10:5"

Just starting out with Expo and decided to dive in with the typescript template using the npx create-expo-app -t expo-template-blank-typescript command. However, I'm running into some JSX type errors that are popping up even though the Expo server see ...

Creating the document.ready function in Angular2 with JQuery

I am seeking assistance to modify the JQuery function so that it can run without the requirement of a button click. Currently, the function only executes when a button is clicked. Code declare var jQuery: any; @Component({ selector: 'home-component ...

What is the best way to incorporate vertical scrolling into a React material table?

I'm having trouble getting vertical scroll to work with my material table in React Typescript. Horizontal scroll is functioning properly for large data, but I'm stuck on implementing the vertical scroll. Here's my code: {isLoading ? ...

Error encountered during Ajax request - two files being transmitted instead of one

Can someone assist me with a basic ajax call for a login button? I need help with the form submission and sending the request to a php file to handle the login action. However, I am encountering an issue where two files are being sent instead of one when ...

nodemon keeps attempting to restart the server after making changes to files, but the updates are not being reflected

I've been using the nodemon package, but I'm experiencing issues with it not restarting the server properly. Instead of showing "server running" after making changes like in tutorials, all it displays is "restarting due to changes". This also res ...

Get rid of the TypeScript error in the specified function

I am currently working on implementing a "Clear" button for a select element that will reset the value to its default state. Here is a snippet of my code: const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => { onChange( ...

The Angular scope remains stagnant even after applying changes

Having trouble updating a variable in the ng-repeat loop <div ng-controller="MapViewCtrl"> <a class="item item-avatar" ng-href="#/event/tabs/mapView" > <img src="img/location.jpg"/> <span cl ...

Allowing several text fields to be paired with multiple checkboxes through a unified jQuery function

I have 4 different checkboxes, each one corresponding to a specific text box. I want to enable the disabled textbox when its corresponding checkbox is checked. Currently, I have written a function for each checkbox in the HTML tag itself using onclick="doc ...

Browsing through tabs to locate specific text

Currently, I am developing a feature for a Chrome extension and I could use some assistance in debugging. The feature involves retrieving a user's input as a string from a text box on popup.html and then scanning through all the open tabs in the curr ...

Why are the class variables in my Angular service not being stored properly in the injected class?

When I console.log ("My ID is:") in the constructor, it prints out the correct ID generated by the server. However, in getServerNotificationToken() function, this.userID is returned as 'undefined' to the server and also prints as such. I am puzz ...

ways to change date format in a React.js application using JavaScript

<b>Choose Date and Time</b><br/> <DateTimeField onChange={this.clockevent} format={"x"}/> clockevent=(newDate)=>{ var dateVal ="/Date("+newDate+")/"; var date = new Date(parseFloat(dateVal.substr(6))); console.log( ...

WebGl - Perspective skewing perspective

I've been working on implementing an oblique projection in WebGL, but I'm encountering an issue where the projection appears identical to ortho. Here's the snippet of code setting up the projection matrix: mat4.identityMatrix(pMatrix); ...

Ways to evaluate and contrast two JSON values in JavaScript by their key names?

I am dealing with two JSON arrays that look like this: array1=[{a:1,b:2,c:3,d:4}] & array2=[{a:2,b:5,c:3,d:4}] Is there a way to determine which key in array2 has the same value as one of the keys in array1? For example, in array 1, key b has a value ...

Is there a way to transform vanilla JavaScript code into Vue.js code?

// Vanilla JS Code Conversion const app = new Vue({ el: '#app', methods: { // Logged out Events loginHelp: function() { this.$refs.forgotten.style.display = 'flex'; this.$refs.login.style.display = 'none&apo ...

Uploading a CSV file in JSON format to Amazon S3 with Node.js

Within my nodejs server.js script, the following code snippet can be found: router.post('/submission', (req, res) => { let data_filtered = req.body.data }) Upon user submission of a csv file, I am successfully retrieving it on the backend. ...

Vue + TypeScript prop type issue: "'Foo' is intended as a type, but is being treated as a value in this context."

As a newcomer to TypeScript and the Vue Composition API, I encountered an error that left me puzzled: I have a component that requires an api variable as a prop, which should be of type AxiosInstance: export default defineComponent({ props: { api: A ...

Error encountered during Jasmine unit testing for the ng-redux @select directive

Here is a snippet from my component.ts file: import { Component, OnInit } from '@angular/core'; import { select } from 'ng2-redux'; import { Observable } from 'rxjs/Observable'; import { PersonalDetailsComponent } from ' ...

Sorting through an array of objects nested within another array of objects

I'm currently working on a MERN app where I retrieve all albums using axios. This is the structure of the data: [ { title: "", artist: "", reviews: [ { username: "", comment: "", }, { ...