Is there a way to destructure values inside a javascript object? - javascript

Lets say I have an object with constants
const FETCH_DATA = {
REQUESTED: 'FETCH_DATA_REQUESTED',
}
And now I want to use them in another object
const fetchData = {
requested() {
return {
type: FETCH_DATA.REQUESTED
};
},
}
But I don't want to retype the whole constant name each time.
Is it possible to do destructure? Something like this, only this doesn't work.
const fetchData = {
{REQUESTED}: FETCH_DATA,
requested() {
return {
type: REQUESTED
}
}
}
I can't put it outside the object due to REQUESTED being too general for the global scope as I have multiple REQUESTED constants.

You can create a module (= file) containing your constants and then export them as constants:
FetchDataContants.ts
export const REQUESTED = 'REQUESTED';
export const MYCONST = 'just an example';
then in another fileā€¦
import { REQUESTED, MYCONST } from './FetchDataConstants';
requested() {
return { type: REQUESTED }
}
or import all like so...
import * as c from './FetchDataConstants';
requested() {
return { type: c.REQUESTED }
}

Related

Circular reference using dependency inversion issue

I have a circular reference issue using this pattern approach. TypeError: Class extends value undefined is not a constructor or null .
The strange thing is, if I move the field.type.ts in src/constants.ts, it doesn't throw an error and it works as expected, but crashes on the Unit Tests. If it leave the fied.type.ts contents in it's own file, it crashes.
Maybe I am not using/understanding this dependency inversion pattern the right way. I could probably fixed by passing the FieldTypeToClassMapping as a parameter in Field.create(options: FieldOptions, fieldTypeMapping: FieldTypeToClassMapping), but I want to understand why this is happening.
import { StringField } from './string.field.model';
import { IntegerField } from './integer.field.model';
...
export const FieldTypeToClassMapping = {
//Constructor of eg. StringField class so I can use `new FieldTypeToClassMapping[options.type](options)`;
[FieldTypeEnum.STRING]: StringField,
[FieldTypeEnum.INTEGER]: IntegerField,
};
//field/field.ts
import { FieldOptions } from 'src/interfaces/field.options.interface';
import { FieldTypeToClassMapping } from 'src/model/template/field.type.to.mapping.ts'
export abstract class Field {
value: any;
type: string;
errors: string[] = [];
public constructor(options: FieldOptions) {
this.value = options.value;
this.type = options.type;
}
public static create(options: FieldOptions): any {
try {
return new FieldTypeToClassMapping[options.type](options);
} catch (e) {
throw new Error(`Invalid field type: ${options.type}`);
}
}
}
//field/integer.field.ts
import { FieldOptions } from 'src/interfaces/field.options.interface';
import { Field } from './field.model';
export class IntegerField extends Field {
constructor(options: FieldOptions) {
super(options);
}
protected validateValueDataType() {
this.validateDataType(this.value, "value");
}
protected validateDefaultDataType() {
this.validateDataType(this.defaultValue, "defaultValue");
}
}
//field/service.ts
payload const postFields = [
{
type: "string", //FieldTypeEnum.STRING,
value: 'a name'
},
];
const postFields = [
{
type: "string",
value: "John",
},
{
type: "integer",
value: 32,
},
];
const fieldsArray = [];
postFields.forEach((item) => {
const field: Field = Field.create(item);
fieldsArray.addField(field);
});
return fieldsArray;
The create(options: FieldOptions) function is defined inside the class Field, but then it tries to instantiate an instance of a class that extends Field.
I think that is where the problem arises. I don't know the entire contents of your files, but I imagine that at the top of any field.type.ts file you import Field. However since Field can instantiate any concrete implementation of itself it would need to know about them so you would need to import everything that extends Field inside Field.
I don't know/understand the dependency inversion pattern well enough to relate it to your question. But given the provided information, perhaps a Factory Pattern is what you need?
You could move the the function create(options: FieldOptions) to a FieldFactory class. Your create function is practically a factory function already.

I can't access modified window.location object jest in another module

I want to mock window.location.search.
config.test.js
import config from './config'
import { MULTIPLE_VIDEOS } from './Constants/flagKeys'
describe('', () => {
const flag = { [MULTIPLE_VIDEOS]: true }
global.window = Object.create(window)
Object.defineProperty(window, 'location', {
value: {}
})
afterAll(() => {
global.window = null
})
it('Mark query string flag as true', () => {
global.window.location.search = `?${MULTIPLE_VIDEOS}=true`
expect(config.flags).toEqual(flag)
})
})
config.js
export default { flags: getFlagsFromQueryString() }
function getFlagsFromQueryString () {
const queryString = qs.parse(window.location.search.slice(1))
const flags = {}
Object.entries(queryString).forEach(([name, value]) => {
flags[name] = value.toLowerCase() === 'true'
})
return flags
}
Though I set search value in location object before calling config.flags, I can't access it inside the function, It always returns empty string.
I want window.location.search.slice(1) to return ?multipleVideos=true inside getFlagsFromQueryString function instead of empty string, since I changed the value in the test file.
One thing I noticed, when I export the function and call in the test file it works.
For this type of complex uses. I would recommend you to create a Window service/util class and expose methods from there. easy to test and mock.
Sample:
// Window.js
class Window {
constructor(configs) {}
navigate(href) {
window.location.href = href;
}
}
export default new Window({});
Now you can easily mock Window.js. It is similar to DI pattern.

Update and Import Dynamic Constants

I have a file defining constants which have the current year as a part of them. In an effort to not update the code every year, I am making them dynamic. The constants can be updated by a dropdown list of years. When the user clicks on a new year, all constants should use that year.
How they are currently being used in a constants file being exported:
export const someConstants = {
CONSTANT_ONE_FOR_YEAR_2018: {
url: CONSTANT_BASE_URL + "more-info-2018",
otherInfo: {
SUPER_CONSTANT_VALUE: "1"
}
}
}
then later imported via
import { someConstants } from "../utils/constants";
console.log(someConstants.CONSTANT_ONE_FOR_YEAR_2018.url);
I was thinking there are a few ways, like changing someConstants to be a function accepting a date, but then that would require a date passed in every time.
export const someConstants = (year) => {
CONSTANT_ONE_FOR_YEAR_2018: {
url: CONSTANT_BASE_URL + `more-info-${year}`,
otherInfo: {
SUPER_CONSTANT_VALUE: "1"
}
}
}
or using a class:
export default class constants {
private _year: number = 2019;
private CONSTANT_BASE_URL: string = "https://";
set year(value: number) {
this._year = value;
}
constantsWithYear = () => {
return {
CONSTANT_ONE_FOR_YEAR_2018: {
url: this.CONSTANT_BASE_URL + `more-info-${this._year}`,
otherInfo: {
SUPER_CONSTANT_VALUE: "1"
}
}
}
}
But then to set and access it, you'd need to have the same instance of the class.
I realize technically constants shouldn't be dynamic, but how do I append a dynamic year to constants to be used throughout my project?
Constants are read-only, therefore you can not modify them later on.
you can use normal variable and make it like this:
`var someConstants = {
CONSTANT_ONE_FOR_YEAR_2018: {
url: CONSTANT_BASE_URL + "more-info-"+new Date().getFullYear(),
otherInfo: {
SUPER_CONSTANT_VALUE: "1"
}
}
}
`

Convincing TS that an object coming through a proxy is never null

I have the following fn that creates a proxy:
const getStorageProxy = (store?: Storage) => {
const proxyObj: { store?: DataStorage } = {};
return new Proxy(proxyObj, {
get(obj, prop) {
if (!obj.store) {
obj.store = new DataStorage(store);
}
return obj.store[prop];
},
});
};
And then I export several instances of this proxy:
const storageA = getStorageProxy(storeA);
const storageB = getStorageProxy(storeB);
export { storageA, storageB };
The reason for this is that I don't want to actually instantiate the DataStorage class unless it's used, and for convenience I want to be able to type:
import { storageA } from 'storage';
This all works. The problem is that according to TS both of my exported storage instances are of type DataStorage | void because of:
const proxyObj: { store?: DataStorage } = {};
They're actually not because of the getter in the proxy, but how do I tell this to TS without actually assigning something to proxyObj.store on instantiation?

Passing a parameter to a function inside a named export object literal

When using a named export to return an object literal composed of functions, is it possible to pass a parameter to one of those functions?
For example, let's say the function below returns conditional results depending on if user's an admin:
// gridConfig.js
function getColumnDefs(isAdmin = false) {
// conditionally return columns
return {
orders: [ ... ],
...
}
}
export const config = {
columnDefs: getColumnDefs(),
rowDefs: getRowDefs(),
...
};
// main.js
import { config } from './gridConfig';
function doStuff() {
const { columnDefs, rowDefs } = config;
grid.columnDefs = columnDefs['orders'];
...
}
If I add the parameter to the function call inside the export, it says the param isn't defined. Adding a parameter to the export alias gives syntax errors. Even if it allowed this, I'm not clear where I'd pass my param inside main.js.
Is there some way of passing a parameter when structuring an export in this manner?
Maybe keeping it simple can be useful :)
export const config = (isAdmin) => ({
columnDefs: getColumnDefs(isAdmin),
rowDefs: getRowDefs(),
...
});
// Import
import { config } from '[...]'; // Placeholder path of import
const myConfigFalse = config(false);
const myConfigTrue = config(true);
export const config = admin => ({
columnDefs: getColumnDefs(admin),
rowDefs: getRowDefs(),
});
// main.js
import { config } from './gridConfig';
function doStuff() {
const { columnDefs, rowDefs } = config(admin);//get the admin variable set before this line
grid.columnDefs = columnDefs['orders'];
...
}

Categories