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.
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
Example problem: I have an Enum variable Difficulty. In a function, I want to set the config DifficultyConfig depending on the value of Difficulty. Here's an inelegant way that I can think of:
export interface DifficultyConfig {
healthModifier: number,
deathIsPermanent: boolean,
}
export interface AppProps {
difficultyConfig: DifficultyConfig
}
export const NormalDifficultyConfig: DifficultyConfig = {
enemyHealthModifier: 1,
deathIsPermanent: false,
}
export const HigherDifficultyConfig: DifficultyConfig = {
enemyHealthModifier: 1.3,
deathIsPermanent: true,
}
export enum Difficulty {
NORMAL = 'Normal',
HARD = 'Hard',
ADVANCED = 'Advanced',
}
function createApp(difficulty: Difficulty) {
let difficultyConfig: DifficultyConfig;
switch(difficulty) {
case Difficulty.NORMAL:
difficultyConfig = NormalDifficultyConfig;
break;
// Both HARD and ADVANCED get HigherDifficultyConfig
case Difficulty.HARD:
difficultyConfig = HigherDifficultyConfig;
break;
case Difficulty.ADVANCED:
difficultyConfig = HigherDifficultyConfig;
break;
default:
difficultyConfig = NormalDifficultyConfig;
break;
}
return new App({
difficultyConfig
});
}
I'm not fond of the Switch case syntax for something so simple. My ideal would be something like this in Scala:
val difficultyConfig = difficulty match {
case Difficulty.NORMAL => NormalDifficultyConfig
case Difficulty.HARD | Difficulty.ADVANCED => HigherDifficultyConfig
case _ => NormalDifficultyConfig
}
Is there an equivalent for this in JavaScript?
If there's a difference in logic, a switch or an if/else if/else (slightly less verbose) is probably the way to go (more on this below though). If it's purely data as in your example, then you could use a difficulty-to-config mapping object:
const difficultyConfigs = {
[Difficulty.NORMAL]: NormalDifficultyConfig,
[Difficulty.HARD]: HigherDifficultyConfig,
[Difficulty.ADVANCED]: HigherDifficultyConfig,
} as const;
You might even declare that as Record<Difficult, DifficultyConfig> like this, as caTS points out in the comments:
const difficultyConfigs: Record<Difficult, DifficultyConfig> = {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[Difficulty.NORMAL]: NormalDifficultyConfig,
[Difficulty.HARD]: HigherDifficultyConfig,
[Difficulty.ADVANCED]: HigherDifficultyConfig,
} as const;
More on that in a minute, but either way, then the function is just:
function createApp(difficulty: Difficulty) {
let difficultyConfig = difficultyConfigs[difficulty];
return new App({
difficultyConfig
});
}
Playground links: Without Record | With Record (I also updated a couple of seeming typos/editing errors in the question's code.)
This also has the advantage that if you add a Difficulty but forget to include one in difficultyConfigs, you get a handy compile-time error when you use it. I've simulated such an error here by adding a MEDIUM difficulty but "forgetting" to update the function.
But better yet, if we include the type Record<Difficulty, DifficultyConfig> type as caTS suggested and difficultyConfigs doesn't have an entry for every Difficulty value, you get an even earlier compile-time error, like this.
Even for logic, if you like you can use the same sort of concept to create a dispatch object:
const difficultyConfigs: Record<Difficulty, () => DifficultyConfig> = {
[Difficulty.NORMAL]: () => { /*...build and return NORMAL config...*/ },
[Difficulty.HARD]: () => { /*...build and return HARD config...*/ },,
[Difficulty.ADVANCED]: () => { /*...build and return ADVANCED config...*/ },,
} as const;
// ...
const difficultyConfig = difficultyConfigs[difficulty]();
In the Relay docs, to fetch a query you have to first preload it using useQueryLoader, and then later pass the result of this into usePreloadedQuery. However, this first step, useQueryLoader takes two arguments: a query itself, which is easy to obtain, and preloadedQueryReference, which the docs do not explain how to obtain. All they say is " e.g. provided by router", but no router actually supports Relay with hooks so this is not helpful information.
As a simple example of this, I have a single component where I want to preload and then use a query:
import {
usePaginationFragment,
usePreloadedQuery,
useQueryLoader
} from "react-relay";
import graphql from 'babel-plugin-relay/macro';
const MY_QUERY = graphql` some stuff here `;
export default function SomeComponent(props) {
const initialRef = ???????;
const [
queryReference,
loadQuery,
disposeQuery,
] = useQueryLoader(AllHits, initialRef);
const qry = usePreloadedQuery(AllHits, queryReference);
}
However without the initialRef, I can't proceed any further. How do I obtain this?
It seems that the query ref is returned from useQueryLoader or loadQuery. The argument to useQueryLoader (initialRef in the original post) is optional, and might be derived from a previous call to loadQuery done by the router, but it in no way required. An example of using useQueryLoader which loads the query as soon as possible is this:
const some_query = graphql` some query here`;
function Parent(props){
// Note, didn't pass in the optional query ref here
const [
queryRef,
loadQuery,
disposeQuery
] = useQueryLoader(some_query);
// Load immediately
useEffect(() => {
loadQuery(
{count: 20},
{fetchPolicy: 'store-or-network'},
);
}, [loadQuery]);
if (!!queryRef) {
return <Child queryRef={queryRef}/>;
} else {
return "loading...";
}
}
export function Child(props) {
const {queryRef} = props;
const loaded = usePreloadedQuery(some_query, queryRef);
const { data } = useFragment(
graphql`some_fragment`,
loaded
);
return JSON.stringify(data);
}
I have this code in a friend of mine React application and I need to understand what this code does explicitly:
const Component = ()=> (
<QueryFetcher>
{({ data }) => {
const { user: { profile = {} } = {} } = data
return (
<div>
{profile.username && profile.username}
</div>
)
}}
</QueryFetcher>
)
What is this line for?
const { user: { profile = {} } = {} } = data
Is it correct to assign something to {} using { user: { profile = {} } = {} } in this functional component? Or in a render() hook of a stateful component in React?
const { user: { profile = {} } = {} } = data basically means that your retrieving the user profile.
const means that you are creating a new variable
{ user: { profile } } } means that you are retrieving profile inside of user
= {} means that if the object is undefined, use an empty object so it will not fail because doing user.profile will throw an error if user is undefined.
= data means that you retrieving this info from the data variable
So, this line means, from the variable data, go take the user, if the user is undefined, use an empty object. Then, go take the profile, if the profile is undefined, use an empty object. Then create a variable called profile with the result. This is like doing this:
const user = data.user === undefined ? {} : data.user;
const profile = user.profile === undefined ? {} : user.profile;
What is this line for?
const { user: { profile = {} } = {} } = data
It's basically just chained ES6 object-destructuring with default values.
What this line does in words:
Read "user" from "data", if "user" is undefined, assign {} as a default value
Read "profile" from "user", if "profile" is undefined, assign {} as a default value
Is it correct
It is mostly a short-hand syntax used to remove repetitive stuff. So instead of accessing multiple object props separately e.g.
this.props.prop1, this.props.prop2, ...
you can use
const { prop1, prop2 } = this.props;
It also helps other readers later quickly understanding what variables are used in a method if all necessary props are destructured at the start.
Please consider the example below
// Example state
let exampleState = {
counter: 0;
modules: {
authentication: Object,
geotools: Object
};
};
class MyAppComponent {
counter: Observable<number>;
constructor(private store: Store<AppState>){
this.counter = store.select('counter');
}
}
Here in the MyAppComponent we react on changes that occur to the counter property of the state. But what if we want to react on nested properties of the state, for example modules.geotools? Seems like there should be a possibility to call a store.select('modules.geotools'), as putting everything on the first level of the global state seems not to be good for overall state structure.
Update
The answer by #cartant is surely correct, but the NgRx version that is used in the Angular 5 requires a little bit different way of state querying. The idea is that we can not just provide the key to the store.select() call, we need to provide a function that returns the specific state branch. Let us call it the stateGetter and write it to accept any number of arguments (i.e. depth of querying).
// The stateGetter implementation
const getUnderlyingProperty = (currentStateLevel, properties: Array<any>) => {
if (properties.length === 0) {
throw 'Unable to get the underlying property';
} else if (properties.length === 1) {
const key = properties.shift();
return currentStateLevel[key];
} else {
const key = properties.shift();
return getUnderlyingProperty(currentStateLevel[key], properties);
}
}
export const stateGetter = (...args) => {
return (state: AppState) => {
let argsCopy = args.slice();
return getUnderlyingProperty(state['state'], argsCopy);
};
};
// Using the stateGetter
...
store.select(storeGetter('root', 'bigbranch', 'mediumbranch', 'smallbranch', 'leaf')).subscribe(data => {});
...
select takes nested keys as separate strings, so your select call should be:
store.select('modules', 'geotools')
I see patterns which make use of a singleton pattern using ES6 classes and I am wondering why I would use them as opposed to just instantiating the class at the bottom of the file and exporting the instance. Is there some kind of negative drawback to doing this? For example:
ES6 Exporting Instance:
import Constants from '../constants';
class _API {
constructor() {
this.url = Constants.API_URL;
}
getCities() {
return fetch(this.url, { method: 'get' })
.then(response => response.json());
}
}
const API = new _API();
export default API;
Usage:
import API from './services/api-service'
What is the difference from using the following Singleton pattern? Are there any reasons for using one from the other? Im actually more curious to know if the first example I gave can have issues that I am not aware of.
Singleton Pattern:
import Constants from '../constants';
let instance = null;
class API {
constructor() {
if(!instance){
instance = this;
}
this.url = Constants.API_URL;
return instance;
}
getCities() {
return fetch(this.url, { method: 'get' })
.then(response => response.json());
}
}
export default API;
Usage:
import API from './services/api-service';
let api = new API()
I would recommend neither. This is totally overcomplicated. If you only need one object, do not use the class syntax! Just go for
import Constants from '../constants';
export default {
url: Constants.API_URL,
getCities() {
return fetch(this.url, { method: 'get' }).then(response => response.json());
}
};
import API from './services/api-service'
or even simpler
import Constants from '../constants';
export const url = Constants.API_URL;
export function getCities() {
return fetch(url, { method: 'get' }).then(response => response.json());
}
import * as API from './services/api-service'
The difference is if you want to test things.
Say you have api.spec.js test file. And that your API thingy has one dependency, like those Constants.
Specifically, constructor in both your versions takes one parameter, your Constants import.
So your constructor looks like this:
class API {
constructor(constants) {
this.API_URL = constants.API_URL;
}
...
}
// single-instance method first
import API from './api';
describe('Single Instance', () => {
it('should take Constants as parameter', () => {
const mockConstants = {
API_URL: "fake_url"
}
const api = new API(mockConstants); // all good, you provided mock here.
});
});
Now, with exporting instance, there's no mocking.
import API from './api';
describe('Singleton', () => {
it('should let us mock the constants somehow', () => {
const mockConstants = {
API_URL: "fake_url"
}
// erm... now what?
});
});
With instantiated object exported, you can't (easily and sanely) change its behavior.
Both are different ways.
Exporting a class like as below
const APIobj = new _API();
export default APIobj; //shortcut=> export new _API()
and then importing like as below in multiple files would point to same instance and a way of creating Singleton pattern.
import APIobj from './services/api-service'
Whereas the other way of exporting the class directly is not singleton as in the file where we are importing we need to new up the class and this will create a separate instance for each newing up
Exporting class only:
export default API;
Importing class and newing up
import API from './services/api-service';
let api = new API()
Another reason to use Singleton Pattern is in some frameworks (like Polymer 1.0) you can't use export syntax.
That's why second option (Singleton pattern) is more useful, for me.
Hope it helps.
Maybe I'm late, because this question is written in 2018, but it still appear in the top of result page when search for js singleton class and I think that it still not have the right answer even if the others ways works. but don't create a class instance.
And this is my way to create a JS singleton class:
class TestClass {
static getInstance(dev = true) {
if (!TestClass.instance) {
console.log('Creating new instance');
Object.defineProperty(TestClass, 'instance', {
value: new TestClass(dev),
writable : false,
enumerable : true,
configurable : false
});
} else {
console.log('Instance already exist');
}
return TestClass.instance;
}
random;
constructor() {
this.random = Math.floor(Math.random() * 99999);
}
}
const instance1 = TestClass.getInstance();
console.log(`The value of random var of instance1 is: ${instance1.random}`);
const instance2 = TestClass.getInstance();
console.log(`The value of random var of instance2 is: ${instance2.random}`);
And this is the result of execution of this code.
Creating new instance
The value of random var of instance1 is: 14929
Instance already exist
The value of random var of instance2 is: 14929
Hope this can help someone