Default string value after call the object in JavaScript

Viewed 87

I have a js object in which I return my endpoint addresses from api. This is a very nice solution for me, it looks like this:

export const API_BASE_URL = 'http://localhost:3000';
export const USERS = '/Users';

export default {
  users: {
    checkEmail: (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`,
    notifications: `${API_BASE_URL}${USERS}/notifications`,
    messages: `${API_BASE_URL}${USERS}/messages`,
  },
};

Now I can call this address in my redux-saga to execute the xhr query:

import { api } from 'utils';

const requestURL = api.users.notifications; 

But I'm a bit stuck because now I have a problem - base path is missing here: '/users'.

Now when I call api.users, then I get a object. I would like to have a default value after calling the object like:

import { api } from 'utils';

const requestURL = api.users; // http://localhost:3000/Users
const requestURL2 = api.users.notifications; // http://localhost:3000/Users/notifications

I know that I could add a new string with the name 'base' to the object and add '/Users' there, but I don't like this solution and I think, there is a better solution.

3 Answers

You could do one of the following:

extend the String class

const API_BASE_URL = "http://localhost:3000"
const USERS = "/Users"

class UsersEndpoints extends String {
  constructor(base) {
    super(base)
  }
  // this is still a proposal at stage 3 to declare instance variables like this
  // if u want a truly es6 way you can move them to the constructor
  checkEmail = (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`
  notifications = `${API_BASE_URL}${USERS}/notifications`
  messages = `${API_BASE_URL}${USERS}/messages`
}

// you can use userEndpoints itself as a string everywhere a string is expected
const userEndpoints = new UsersEndpoints(API_BASE_URL)
export default {
   users: userEndpoints
}

The previous is just actually equivalent to

...
const userEndpoints = new String(API_BASE_URL)
userEndpoints.notifications = `${API_BASE_URL}${USERS}/notifications`
...

Obviously this is not recommended: you should not extend native classes, there are many disadvantages to this approach. An obvious example is that there could be a conflict between the properties you use and the properties that might be brought by the native class

override the toString method

...
export default {
  users: {
    checkEmail: (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`,
    notifications: `${API_BASE_URL}${USERS}/notifications`,
    messages: `${API_BASE_URL}${USERS}/messages`,
    toString: () => API_BASE_URL
  },
};
// this is actually not much different than the previous method, since a String is an objet with an overridden toString method.
// That said this method is also not recommended since toString is used in many places in native code, and overriding it just to substitute a string value will make information get lost in such places, error stacks for example

Achieve what u want using the language features intended for such a use case

What you are asking is to make the same variable to have different values in the same time, which is not possible in the language syntax, and it makes sense because it makes it hard to reason about code.

that being said i recommend something of the following nature

// it is also better to use named exports
export const getUsersEndpoint = ({
  path = "",
  dynamicEndpointPayload = {},
} = {}) => {
  switch (path) {
    case "notifications":
      return `${API_BASE_URL}${USERS}/notifications`
    case "messages":
      return `${API_BASE_URL}${USERS}/messages`
    case "checkEmail":
      return `${API_BASE_URL}${USERS}/${dynamicEndpointPayload.email}/checkEmail`
    // you still can do checkEmail like this, but the previous is more consistent
    // case "checkEmail":
    //   return (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`
    default:
      return `${API_BASE_URL}`
  }
}

// you can use it like this
getUsersEndpoint() // returns the base
getUsersEndpoint({path: 'notifications'})

You can extend prototype to achieve this behaviour:

export const API_BASE_URL = 'http://localhost:3000';
export const USERS = '/Users';

const users = `${API_BASE_URL}${USERS}`

const baseUrls = {
  checkEmail: (email) => `${users}/${email}/checkEmail`,
  notifications: `${users}/notifications`,
  messages: `${users}/messages`,
}

Object.setPrototypeOf(users.__proto__, baseUrls);

export default {
  users
};

Try having object will all user endpoint and a function that return a value of a end point

    const user = {
    default: '/users',
    notification: '/notification',
    profile: '/profile',
    getEndPoint(prop) {
    if(this[prop] === 'default' ){
        return this[prop];
     }   else {
        if(this[prop]) {
            return  this.default + this[prop];
        }
     }

     }
   }

So you can have more end points that come under user and you can simply call

const requestURL = api.user.getEndPoint('default'); // http://localhost:3000/Users
const requestURL2 = api.user.getEndPoint('notifications'); // http://localhost:3000/Users/notification
Related