SharePoint React pnpjs - reactjs

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);

Related

Creating a Generic React Forward Ref component unassignable to the constraint

I'm trying to create a Generic Forward Ref component that accepts a value of generic type and a render function that also needs a value property from that same generic type.
I get the Typescript error: TS2322: Type 'Product' is not assignable to type 'T'.   'Product' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Draggable'. (L34 # https://github.com/pieterjandebruyne/generic-forward-ref-issue/blob/master/src/GenericForwardRefComponent.tsx)
I already found some posts on stack overflow explaining what and why it could go wrong but still I feel that none of the explanations really apply to this usecase..
I created a public repo to recreate the issue # https://github.com/pieterjandebruyne/generic-forward-ref-issue
Files that are being used are:
App.tsx -> calls the GenericForwardRefComponent
GenericForwardRefComponent.tsx -> should render the ProductRow and pass the generic type
Interfaces.ts -> all types
ProductRow.tsx -> component that should be rendered
The ultimate goal is that I could also have for example a CategoryRow (with value of type Category (that has id: string)) that I could pass to the GenericForwardRefComponent
Is this something that would be possible or am I just trying to stretch the current limits of React/TS? Also I would love to hear an explanation why this error occurs as I don't see the error that could happen with this implementation. This stackoverflow probably addresses this but I can't figure out why/which part triggers it in my code..
How to fix TS2322: "could be instantiated with a different subtype of constraint 'object'"?
I used the generic forward ref approach I found # https://dirask.com/posts/React-forwardRef-with-generic-component-in-TypeScript-D6BoRD
You can see that with this inside the function with this header
const x = <T extends Draggable>(
{
dragOverlay,
renderItem,
value
}: GenericForwardedRefComponentProps<T>,
reference: ForwardedRef<HTMLDivElement>
)
you call this function:
return renderItem({
value,
reference
})
If i simplify, the renderItem has type (value: T) => ... but then in the body, the type of value is always Product.
When the function would be called with T = Product, that would work fine, but consider, that the caller of this function can call the function with T = MyType like this :
interface MyType extends Draggable {
my_prop: string
}
x<MyType>({ renderItem: (x: MyType) => { x.my_prop }, value: { name: "XXX" } })
You can see, that in the function renderItem I'm accessing a field, that is not present in the product type. That's why there is this error. I have fulfilled the contract of the function, but the result is that I'm accessing missing fields.
Anyway, the function is called only once, so you can remove the type parameter and like this:
(
{
dragOverlay,
renderItem,
value
}: GenericForwardedRefComponentProps<Product>,
reference: ForwardedRef<HTMLDivElement>
) => {
Then, typescript will be satisfied.

How to narrow down union of generic types in Typescript?

I'm trying to create a form component in React that uses generic types.
The generic type for the form is the initialValues that are passed in.
I can't for the life of me seem to get Typescript to narrow down the type that corresponds to a particular field.
Here's a CodeSandbox with one of my many attempts at trying to get this to work correctly.
I've tried many other things, like adding a 'type' property to each of the fields and then using a switch statement to try and narrow it down that way, but none of them have seemed to work!
Not looking anyone to write this code for me, but a nudge in the right direction would be greatly appreciated.
So as you've probably guessed by now, to do this you can't just define an indexable type like
type FormValues<T> = {
[key: string]: T
}
Because T in this case will be a union that doesn't help too much.
So you can't use indexable type, now the only way I can think stop trying to create the type yourself and just let typescript infer it for you.
Firstly let me change your FormValue declaration a bit, I'll remove ValueOf, while I was playing with your sandbox I forgot what was its purpose, but if I don't think it matters in the way I'm suggesting. So let's type it like this:
type FormValue<TName extends string, TValue> = {
value: TValue
name: TName
pristine: boolean
errors: string[] | null
}
Now this will just take two type arguments, one is the name, another is the value, no unnecessary objects. Now let's type InitialFormValues: it will be just an object with random properties, we won't use it to type anything, only to constraint types:
type InitialValues = {
[key: string]: any
}
// or
type InitialValues = Record<string, any>
Next comes UseFormProps. Here we avoid creating indexable type, that doesn't lead to anything, but we let typescript infer the type for us. We only need to constraint it a bit so that it's not possible to pass p.e. a number as initialValues:
type UseFormProps<T extends InitialValues> = {
initialValues: T
}
Now typescript will automatically choose T and it will be exactly what we need: if initialValues is { foo: 1, bar: 'str' }, it will be { foo: number, bar: string } and nothing more.
Last goes FormValues:
type FormValues<T extends InitialValues> = {
[x in keyof T]: FormValue<x, T[x]>
}
Now FormValues will not be just indexable, but it will contain exactly the properties T contains, also T[x] will not be a union like T[keyof T], it will be different for each value of x so for each property. That's basically it, now it should work as expected. You'll only need to change something in transformInitialValues, because typescript will now complain if you assign empty object to FormValues<whatever>

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

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.

Typescript errors with Union type in Redux component

I have a particularly trying error cropping up in one of my Action Creator.
let approveEnquiry = (): AppThunkAction<StoreAction | AppThunkAction<any> | RouterAction> => (dispatch, getState) => {
// Dispatch action then call the route
};
interface StoreAction {
type: ActionType.SET_VALUE;
fieldName: string;
fieldValue: any;
}
And RouterAction comes from the react-redux-router type definitions and is of the form:
interface RouterAction {
type: ...
}
The problem seems to be the Union type cannot correctly differentiate between the RouterAction interface and the StoreAction interface.
The vexing thing is this appears to work on a colleagues machine and not on mine.
We are using Webpack and typescript v2.4.2
The error we are getting is warning that when attempting to dispatch an action which is part of the reducer it is expecting payload to be defined. This is because both RouterAction and Redux Action interfaces have a type property.
The solution was to include a cast for the item to the intended type to ensure that the correct part of the union type was used.
So in my action creator method:
dispatch(<StoreAction>{
type: ActionType.SET_VALUE,
fieldName: '<FIELD_NAME>',
fieldValue: '<FIELD_VALUE>'
});
This prevented the type system from thinking I was attempting to define a RouterAction. Without the type cast the warning suggested that I should have a payload property.
Just to note the reason we are adding the AppThunkAction<any>. The reason this is there is so I can dispatch actions which are part of other stores. So I can make a call like dispatch(actionCreators.someAction());. I am not attempting to call actions recursively, which I agree would not be desirable.
Wondered if anyone could share thoughts on how union types work when separate types in the union have one or more identical properties.
Thanks

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