How to fix linting error with React's setState() - reactjs

I am trying to figure out an error I get when linting my React TypeScript project. The problem is within a component when setting the state:
this.setState({dialogStatus: DialogNoConfigurationFileStatus.Create});
The error I get when linting:
Property 'creationStep' is missing in type '{ dialogStatus: DialogNoConfigurationFileStatus.Create; }'.
Error at src/dialogs/dialog-no-configuration-file.tsx:137:23: Argument of type '{ creationStep: number; }' is not assignable to parameter of type 'DialogNoConfigurationFileState'.
The structure of the component:
export interface DialogNoConfigurationFileState {
dialogStatus: DialogNoConfigurationFileStatus;
creationStep: number;
}
export class DialogNoConfigurationFile extends React.Component<DialogNoConfigurationFileProps, DialogNoConfigurationFileState> {
public constructor(props: any) {
super(props);
this.state = {
dialogStatus: DialogNoConfigurationFileStatus.Enter,
creationStep: 0
};
}
...
}
My assumption is that I get the linting error, because both params within the interace DialogNoConfigurationFileState are obligatory. When setting them both to optional the error is gone:
export interface DialogNoConfigurationFileState {
dialogStatus?: DialogNoConfigurationFileStatus;
creationStep?: number;
}
I would like to leave them obligatory though. The reason for that is, because I want to force the creation of the state within the constructor. This way I am sure the state is set when accessing this.state.creationStep from within the component. If I set the state param to optional, I have to check first if the state and the state's params are assigned. A step I would like to avoid.
What is the best thing to be done here? Am I unconsciously creating bad code here? Even though linting fails, the code still runs well, but the people behind TSLint must have had a thought behind this error.
Could someone please elaborate?
EDIT 1:
This the enum DialogNoConfigurationFileStatus:
export enum DialogNoConfigurationFileStatus {
Enter,
Create,
}

If you're using the latest TypeScript definitions for React (16.8.5 at time of writing) then this shouldn't be an issue any more - older definitions don't allow 'partial' updates of state but this was fixed at some point.
If it's not possible to update, and you really want to keep the fields obligatory, you can use casting as a workaround to make the type error go away
this.setState({
dialogStatus: DialogNoConfigurationFileStatus.Create
} as DialogNoConfigurationFileState);
This essentially tells TypeScript:
Trust me, this is a valid DialogNoConfigurationFileState, I know what I'm doing.

Related

TypeScript: how to send array as a prop?

I am sending an array of data to a component as a prop, like:
<InfoTable TableInfo={tableRows} />;
Where tableRows is the array.
On my InfoTable component file I define my props like
interface InfoTableProps {
tableInfo: TableInfo[];
}
Which allows me to .map() through the tableInfo array, eg:
let tableRow = tableInfo.map(function(tableInfoRow) {
// Do some stuff
}
This works fine. However, my compiler gets a warning on tableInfo: TableInfo[];
Cannot find name 'TableInfo'. TS2304
I've tried Googling the problem of course but I just get people asking the same question.
Would anyone know how to remove this error or what it means?
Don't you need to define the TableInfo type somewhere?
eg
Interface TableInfo {
id: number
name: string
}
Sorry If you've already done that and its something else :-)

How to remove TS2722: Cannot invoke an object which is possibly 'undefined'. error?

I have a function looking like this
let bcInst;
if(props.onBoundsChanged){
bcInst = kakao.maps.event.addListener(map, 'bounds_changed',()=>{
props.onBoundsChanged(map); //I get error here
})
}
props interface looking like below
interface IMapProps{
mapId?: string;
longitude: number;
latitude: number;
level:number;
onBoundsChanged?:{
(map:any) : void
}
}
Even if I am checking for props.onBoundsChanged in an if statement, I am getting TS2722: Cannot invoke an object which is possibly 'undefined'. error at the position I am using props.onBoundsChanged. How do I solve this issue?
TypeScript is saying your onBoundsChanged might be undefined because you have defined it as an optional (?) property in the interface IMapProps:
onBoundsChanged?: {}
Your check for onBoundsChanged for a truthy value is correct, but the event listener will be invoked at a later time, which means onBoundsChanged might be undefined like #basarat specified. You can avoid this error by using one of these inside your listener.
Check for truthy value before calling onBoundsChanged
if(props.onBoundsChanged) onBoundsChanged(map)
Use optional chaining ?. operator to access onBoundsChanged
props?.onBoundsChanged(map)
Some useful learning resources:
You can read more about truthy values from the MDN here.
https://developer.mozilla.org/en-US/docs/Glossary/Truthy
More about the optional chaining ?. operator here.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
I have the same issue as below. In my case, my code is
handleClick={(p: ActionFieldClickParam) =>
this.props.handleClick({ p, index })
}
then, I solved by adding '?.' and changed function call like this.props.handleClick?.({ p, index })
In your case, you should add next to the call function:
props.onBoundsChanged?.(map)
Because you event listener might be invoked much later than you have done the check, TypeScript is complaining
Fix
Store the reference that TypeScript knows to be valid e.g.
let bcInst;
if(props.onBoundsChanged){
const {onBoundsChanged} = props; // STORE
bcInst = kakao.maps.event.addListener(map, 'bounds_changed',()=>{
onBoundsChanged(map); // OKAY
})
}
Alternatively move the check inside the listener.

SharePoint React pnpjs

I am working on a react project for SharePoint framework.
My question is a more or less general question.
I came across a certain problem which I don't understand:
I tried to use sp.search with an object matching the SearchQuery interface, but did this in a class extending react.component
Code:
public search(query: string, properties: Array<string>, rowLimit: number): Promise<SearchResults> {
return new Promise<SearchResults>(async (resolve) => {
let result = await sp.search(<SearchQuery>{
Querytext: query,
SelectProperties: properties,
RowLimit: rowLimit
});
resolve(result);
});
}
As you can see, this is just a basic search function. If I use sp.search in a class that does not extend react.component or if I don't use the searchquery instance, but a plain querystring everything works fine.
I seem to be a type error, but honestly, I don't get what's happening.
Error:
[ts]Argument of type 'Element' is not assignable to parameter of type 'SearchQueryInit'. Type 'Element' is not assignable to type 'ISearchQueryBuilder'.
Property 'query' is missing in type 'Element'. [2345]
[ts] JSX element 'SearchQuery' has no corresponding closing tag. [17008]
[ts] 'SearchQuery' only refers to a type, but is being used as a value here. [2693]
So I decided to just write a service that's using pnpjs and that's totally fine, especially since I didn't plan to use pnpjs in a component, that was just to be a bit faster and have something to test and to work with. but nevertheless, I really would like to understand what is going on. Btw I'm using TypeScript, if that's of interest.
Apparently those errors occur due to a limitation cased by the use of <T> for generic type parameter declarations combined with JSX grammar, refer this issue for a more details (which is marked as a Design Limitation)
To circumvent this error, create the instance of SearchQuery:
const searchQuery: SearchQuery = {
Querytext: query,
SelectProperties: properties,
RowLimit: rowLimit
};
and then pass it into sp.search method:
let result = await sp.search(searchQuery);

Why does `tslint` report `TS2531: Object is possibly 'null'.` even I checked it before use?

I am using typescript in React project. I have below code in typescript:
if(this.state.deletedItem !== null) {
this.props.deleteItem({refItemIdx: this.state.deletedItem.itemIdx});
}
tslint gives me an error TS2531: Object is possibly 'null'. on the object this.state.deletedItem.itemIdx. It says this.state.deletedItem could be null. But I already checked it in the if condition and why it still reports such error?
Below is the type definition:
interface State {
deletedItem: ItemDiscountTranItem | null;
}
export class MainView extends React.Component<Props, State> {
state = {
deletedItem: null,
};
...
I have tried to update the code as below but still get the error:
if(this.state.deletedItem) {
this.props.voidItemDiscount({refItemIdx: this.state.deletedItem ? this.state.deletedItem.itemIdx : 0});
}
I tried below syntax and still get the same error:
this.props.voidItemDiscount({refItemIdx: this.state.deletedItem && this.state.deletedItem.itemIdx});
interface State {
deletedItem: ItemDiscountTranItem | null;
}
This line indicate that:
+ deletedItem have to be in the State
+ The deletedItem could be null
Since it could be null and have to be there at the same time
When you call {refItemIdx: this.state.deletedItem.itemIdx}
the deletedItem could be null then javascript will throw error, something like this can not read property itemIdx of null, sort of.
typescript help you indicate that problem first with the TS2531: Object is possibly 'null'. to preventing further error.
So you should declare your interface like this:
interface State {
deletedItem?: ItemDiscountTranItem;
}
{refItemIdx: this.state.deletedItem && this.state.deletedItem.itemIdx}
This indicate that:
+ You could or could have the deletedItem in your interface
+ And the deletedItem is type of ItemDiscountTranItem
And you can get the value through checking whether the this.state.deletedItem is exist or not then get the itemIdx out of it.
=====================
Here is just my thought, it's good if you consider using it.
That is, I usually let the variables or something that related to the global context or application state in your case is State
Have a default value and reduce as much null and undefined as possible. You can declare your state like this
deletedItems: ItemDiscountTranItem[],
There is no harm in this declaration, and in fact, it help your code more readable and extendable,
Eg: you can check whether there is anything to delete by simply if(state.deletedItems.length)
and you can even further loop throw the list of deletedItems.forEach
what next? you don't even have to scratch your head about when this should be null is it undefined? or when should I check that?, you can leave all of that behind.
Because it now is simple, you want to delete something? you check if there is something to delete, you want to delete more? you loop. simple enough.
The problem about my code is that I declared my component class as class MainView extends React.Component<Props, State>. The State here only indicate that the component state extends from this class. So the component state defined below has a narrow type of State which is null.
state = {
deletedItem: null,
};
below change fixed my problem:
state: State = {
deletedItem: null,
};

flow type error on type State and type Props

I am using flow, and mostly things are working out, but I am catching this error in my linting on something as trivial as this:
type Props = {
actions: object,
products: object,
};
type State = {
email: string,
name: string
};
class SomeComponent extends Component {
// etc..
}
The linting errors show right after the "type" keyword and the error is:
"Expecting newline or semicolon"
There are 2 possibilities that I see here:
1) object should be capitalized (Object)
2) You are not using eslint-plugin-flowtype
This may seem silly, but I had to go into IntelliJ IDEA -> Preferences -> Languages & Frameworks -> JavaScript and change JavaScript language version to Flow (was previously React JSX). I was already using eslint-plugin-flowtype, and was wondering why it was not working.

Resources