Object is possibly undefined is not accepted with question mark - javascript

Consider the following ocde:
app-config.json
{
"auth": {
"clientId": "acb610688b49",
},
"cache": {
"cacheLocation": "localStorage"
},
"scopes": {
"loginRequest": ["openid", "profile", "user.read"]
},
"resources": {
"gatewayApi": {
"resourceUri": "https://localhost:44351/api",
"resourceScope": ["api://0e01a2d8/access_as_user"]
}
}
}
authService.js
import { isInternetExplorer } from 'src/services/utils/utilsService'
import * as Msal from 'msal'
import * as configJson from 'src/app-config.json'
type resourceType = {
resourceUri: string
resourceScope: string | string[]
}
type resourcesType = {
[key: string]: resourceType
}
interface JsonConfigInterface extends Msal.Configuration {
scopes: {
loginRequest: string[]
}
resources: resourcesType
}
const config: JsonConfigInterface = configJson as JsonConfigInterface
function MSALConfigFactory(): Msal.Configuration {
return {
auth: {
clientId: config.auth.clientId,
},
cache: {
cacheLocation?: config.cache.cacheLocation as Msal.CacheLocation,
},
}
}
The reported error for cacheLocation?: config.cache.cacheLocation as Msal.CacheLocation is:
(property) cache?: CacheOptions | undefined Object is possibly
'undefined'.ts(2532)
When looking at the msal documentation it says:
export type CacheLocation = "localStorage" | "sessionStorage";
export type CacheOptions = {
cacheLocation?: CacheLocation;
storeAuthStateInCookie?: boolean;
};
The question mark in cacheLocation? indicates that this parameter is optional, which is fine. It can or cannot be defined within the JSON file. So I do not understand why TypeScript complains that it can be undefined when that is an accepted value? Of course, the TS null check is in place, but should it not allow this as there is a question mark?
The current workaround for now is below, but I don't know if that is the correct approach:
// eslint-disable-next-line #typescript-eslint/no-non-null-assertion
cacheLocation: config.cache!.cacheLocation as Msal.CacheLocation,
I'm still a beginner, so thank you for your help.

Typescript is trying to tell you that this property access might result in an error:
config.cache.cacheLocation
If either config or config.cache turn out undefined, an exception will be raised.
You can do this:
config?.cache?.cacheLocation
It seems this is the type:
export type Configuration = {
auth?: BrowserAuthOptions,
cache?: CacheOptions,
system?: BrowserSystemOptions
};

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.

Typescript throws error for return function type

I'm very new to typescript. I am trying to type this mock function and it's throwing in the following error:
Value of type '() => { doc: { name: string; header: string; body:
string; category: string; isFunction: boolean; isOperator: undefined;
supportedExecutionContexts: string[]; }; error: undefined; }' has no
properties in common with type 'IQuickHelpDocs'. Did you mean to call
it?ts(2560)
getHelpDocs.ts(27, 49): Did you mean to call this expression?
export const getHelpDocs: IHelpDocs = () => ({
doc: {
name: 'demo',
header: 'demo',
body: 'Returns the demo value of <code>value</code>',
category: 'Number',
isFunction: true,
isOperator: undefined,
supportedExecutionContexts: ['calc', 'my'],
},
error: undefined,
})
Types.ts
export interface IHelpDocs {
doc?: IDoc
error?: IDocsError
}
Not sure what I am missing. So confused. Please kindly help.
The annotation you've currently written says that getHelpDocs is of type IHelpDocs:
export const getHelpDocs: IHelpDocs = ...
What you probably wanted to convey instead is that it's a function that takes no arguments and returns IHelpDocs:
export const getHelpDocs: () => IHelpDocs = ...
What may be confusing here is the type annotation. For functions, you can annotate the return type as follows:
export function getHelpDocs(): IHelpDocs { ... }
For variables, you'll need to annotate the whole shebang, otherwise Typescript won't know to expect a function — it could just as well be that you did want to have just the interface.
You are telling TypeScript getHelpDocs should be in the shape of IHelpDocs, which it is not. It's a function that returns a IHelpDocs.
I would change this to export a function directly:
export function getHelpDocs(): IHelpDocs {
return {
doc: {
name: 'abs',
header: '<code>abs(value)</code>',
body: 'Returns the absolute value of <code>value</code>',
category: 'Number',
isFunction: true,
isOperator: undefined,
supportedExecutionContexts: ['calc', 'sql'],
},
error: undefined,
};
}

How to disable/assert/override inferred types when importing JSON in typescript

I am using the "resolveJsonModule" feature in typescript 2.9 in order to import a json file. Let's say the json file looks like this:
{
"Nabokov": {
"Pale Fire": {
"Pages": "200",
"Edition": "Paperback",
},
"Pnin": {
"Pages": "150",
"Edition": "Hardcover"
},
"Lolita": {
"Pages": "150",
"Edition": "Paperback",
"Year": "1955"
}
},
"Joyce": {
"Ulysses": {
"Pages": "800",
"Language": "English"
},
"Finnegan's Wake": {
"Pages": "1200",
"Language": "Gibberish"
}
}
}
and i am importing it by:
import catalog from '../resources/catalog.json'
Okay, so when this gets imported, typescript will automatically define types for this. This becomes problematic for me when I am trying to write, for example, a function that can return the info for a author/book.
I want to just do
function getBook(author: string, title: string) {
return catalog[author][title]
}
I get a "Element implicitly has an 'any' type because expression of type 'string' ..." So it wants me to define author as "Nabokov" | "Joyce" but
since the JSON file will be forever expanding, and I don't know what's going to be in it at any time, I want to "genericize" the type for the imported object so that it's just something like [key: string].
Try the following:
type C = typeof catalog;
function getBook<T extends keyof C>(author: T, title: keyof C[T]) {
return catalog[author][title]
}
getBook("Nabokov", "Lolita"); // OK
getBook("Joyce", "Lolita"); // Not OK
Here is a playground to demonstrate.
Update
Based on your use case, you shouldn't restrict the types of the parameters, and you should just do this:
function getBook(author: string, title: string) {
try {
return (catalog as any)[author][title]
} catch(e) {
// do error handling here
}
}
Update 2
The method mentioned above is more or less bypassing TypeScript. If one really want to ensure type-safety, here's the genuine TypeScript approach to do the same thing.
type B = {
Pages: string;
Edition?: string;
Year?: string;
Language?: string;
};
type C = {
[index: string]: {
[index:string]: B
}
};
function getBook(author: string, title: string): B | undefined {
const cat: C = catalog;
if(author in cat && title in cat[author]) {
return cat[author][title];
} else {
// do error handling here
return undefined;
}
}
See this playground.

Types of property 'cacheLocation' are incompatible

I have an old app in react with javascript, but I started a new one, to slowly migrate the .JS code to Typescript.
The first file I want to migrate its a configuration file, when its .js build succeeds.
WHen renamed to .TS I get this error
/Users/xx/yy/lulo/src/adalConfig.ts
(13,54): Argument of type '{ tenant: string; clientId: string; endpoints: { api: string; }; 'apiUrl': string; cacheLocation: string; }' is not assignable to parameter of type 'AdalConfig'.
Types of property 'cacheLocation' are incompatible.
Type 'string' is not assignable to type '"localStorage" | "sessionStorage" | undefined'
The file is this:
import { AuthenticationContext, adalFetch, withAdalLogin } from 'react-adal';
export const adalConfig = {
tenant: 'xxxx-c220-48a2-a73f-1177fa2c098e',
clientId: 'xxxxx-bd54-456d-8aa7-f8cab3147fd2',
endpoints: {
api:'xxxxx-abaa-4519-82cf-e9d022b87536'
},
'apiUrl': 'https://xxxxx-app.azurewebsites.net/api',
cacheLocation: 'localStorage'
};
export const authContext = new AuthenticationContext(adalConfig);
export const adalApiFetch = (fetch, url, options) =>
adalFetch(authContext, adalConfig.endpoints.api, fetch, adalConfig.apiUrl+url, options);
export const withAdalLoginApi = withAdalLogin(authContext, adalConfig.endpoints.api);
The issue is that the type of adalConfig gets asserted instead of being defined. You can read more about it in the docs but it basically means that TypeScript guesses the type. Short example:
type FooBar = 'foo' | 'bar';
const fooBar1 = 'foo'; // fooBar1: 'foo'
const fooBar2: FooBar = 'foo'; // fooBar2: FooBar
TypeScript playground link
Type assertion depends on a whole bunch of stuff and it's really hard to guess by hand which type TypeScript is going to assert. It's really useful to write TS code quickly, though. In any case - the problem in your code is that adalConfig.cacheLocation gets asserted to a string, but instead you want TypeScript to understand that its type is compatible with "localStorage" | "sessionStorage" | undefined.
Two ways to do that:
cacheLocation: 'localStorage' as 'localStorage': will precise to TypeScript that cacheLocation is of type 'localStorage', thus compatible with what you want
export const adalConfig: AdalConfig = ... will precise to TypeScript that the whole adalConfig object is of type AdalConfig, so it has basically the same effect
Kudos to #explosion-pills and #zerkms who contributed in the comments to this question
I know this is an oldie but always helpful to post an update. You can import configuration from the msal library to set the type of the config variable.
import { MsalAuthProvider, LoginType } from 'react-aad-msal';
import { Configuration } from 'msal';
// Msal Configurations
const config: Configuration = {
auth: {
authority: 'https://login.microsoftonline.com/',
clientId: '<YOUR APPLICATION ID>'
},
cache: {
cacheLocation:"localStorage",
storeAuthStateInCookie: true,
}
};
// Authentication Parameters
const authenticationParameters = {
scopes: [
`<your app registartion app id>/.default`
]
}
// Options
const options = {
loginType: LoginType.Popup,
tokenRefreshUri: window.location.origin + '/auth.html'
}
export const authProvider = new MsalAuthProvider(config, authenticationParameters, options)
import { CacheLocation } from "#auth0/auth0-react";
const AUTH0_CASH_LOCATION: CacheLocation | unefined = "localstorage";
this will help you. Auth0 provider makes sure that caching should save in memory or localStorage so you should provide either localStorage or memory for cacheLocation option

Typescript + immutability-helper #set

I've come to join the typescript wagon and i wantede to keep using this immutability-helper lib when im updating my redux store, but for some reason now i get this error when im trying to execute a update?:
[ts] Argument of type '{ flags: { hideChat: { $set: boolean; }; }; }'
is not assignable to parameter of type 'Spec'.
Object literal may only specify known properties, and 'flags' does not
exist in type 'Spec'.
export interface GlobalStateInit {
flags: {
hideChat: boolean
}
}
const initialState: GlobalStateInit = {
flags: {
hideChat: false
}
}
const reducer: Reducer<GlobalStateInit, GlobalAction> = (state = initialState, action) => {
switch (action.type) {
case getType(actions.toggleChat):
return update(state, {
flags: {
hideChat: { $set: !state.flags.hideChat }
}
})
default:
return state
}
}
export { reducer as GlobalReducer }
I was asuming this should be trivial and should just work out of the box, my jest test running in the background can figure this out but the VScode TS Linter gets a bit angry.
Not sure if this is a bug or if its just VScode that messes up.
This is a deliberate compiler feature. Here's a short version of the same problem:
interface Example {
name: string,
val: number
}
const example: Example = {
name: 'Fenton',
val: 1,
more: 'example'
}
The more property isn't part of the Example interface, so the compiler is going to warn you that you might have made a mistake. For example, if there is an optional member that you typed wrong, it will catch that for you:
interface Example {
name: string,
val: number,
side?: string
}
const example: Example = {
name: 'Fenton',
val: 1,
sdie: 'Left' // Oops, I meant "side"
}
In cases where you think you know better than the compiler, you can tell it that you're in charge:
interface Example {
name: string,
val: number
}
const example = {
name: 'Fenton',
val: 1,
more: 'example'
} as Example
This allows additional members, while still ensuring you don't make a mistake such as:
// Lots of errors!
const example = {
more: 'example'
} as Example

Categories