Flow documentation only shows how to declare a custom Higher Order Component to work with custom Class and its Props. In my case I have a custom Class like:
type Props = {
navigation: Object,
isFocused: boolean
}
type State = {
config: AppConfig,
pack: Package,
init: boolean,
}
class MainAppScreen extends React.Component<Props, State> {
...
}
export default withNavigationFocus(MainAppScreen);
and want to wrap it with external HOC from 'react-navigation';
What should I do (beside //$FlowFixMe) to get rid of this message:
Error:(111, 16) Cannot build a typed interface for this module. You should annotate the exports of this module with types. Cannot determine the type of this call expression. Please provide an annotation, e.g., by adding a type cast around this expression. (signature-verification-failure)
Thanks.
UPDATE:
As #user11307804 pointed into the right direction, I have been also trying involving flow-typs for external libraries. However it still seems not to be possible to import a type for a (HOC) function like:
declare export function withNavigationFocus<Props: {...}, ComponentType: React$ComponentType<Props>>(
Component: ComponentType
): React$ComponentType<$Diff<React$ElementConfig<ComponentType>, {| isFocused: ?boolean |}>>;
When I try to import it like: (following this example)
import type {withNavigationFocus} from 'react-navigation';
I get the error:
Error:(22, 14) Cannot import the value `withNavigationFocus` as a type. `import type` only works on type exports like type aliases, interfaces, and classes. If you intended to import the type of a value use `import typeof` instead.
and if I try with typeof I get:
import typeof {withNavigationFocus} from 'react-navigation';
I get the errors:
Error:(22, 16) Cannot declare `withNavigationFocus` [1] because the name is already bound.
Error:(112, 16) Cannot call `withNavigationFocus` because Named import from module `react-navigation` [1] is not a function.
Error:(112, 16) Cannot build a typed interface for this module. You should annotate the exports of this module with types. Cannot determine the type of this call expression. Please provide an annotation, e.g., by adding a type cast around this expression. (`signature-verification-failure`)
Thanks.
Flow is complaining that withNavigationFocused is untyped. Fortunately, the flow-typed project has react-navigation types. (There are other definition files for different version of react-navigation or flow; the one I've linked is for react-navigation^4.0.0 and flow^0.114.0.) You can include the library definition in your project following the Library Definitions documentation (essentially, just save the file in <PROJECT_ROOT>/flow-typed directory).
Related
I have a custom type declaration file, in which I would like to override an already existing interface property (not create a new property, but modify existing)
I have tried several approaches, such as Omiting the property (shown below), with no luck.
src/types/example.d.ts
----------
import package, { AnInterface } from 'example'
interface CustomInstance extends Omit<Instance, 'clients'> {
clients: string[];
}
declare module "example-module" {
export interface Instance extends CustomInstance {
test: number
clients: CustomType
}
}
src/main.ts
-----------
example.test // number as expected
example.clients // is always the type from node_modules, not my .d.ts file
If I try to add clients without doing any extending, I getSubsequent property declarations must have the same type. Property 'clients' must be of type 'EDictionary', but here has type 'CustomType'
however if I try to extend the custom instance (as above) I get the error Type 'Instance' recursively references itself as a base type. (which even if I ts-ignore, my application will still use the type defined in node_modules).
You can't un-define something with d.ts, you can only define it more precisely.
Best you can to remove something is re-export everything from your module, changing its types
In your case you may want to not un-define clients but to narrow it down, then that will be
import { EDictionary } from 'example-module';
declare module "example-module" {
export interface Instance { // i.e. extends example-module.Instance
test: number
clients: CustomType & EDictionary // narrow it down in a compatible manner
}
}
I am attempting to write a type declaration for an NPM package (or more specifically an untyped directory within a package) my project depends on.
The package itself is react-big-calendar and it doesn't bundle its own types, however there is #types/react-big-calendar which provides types for the main package, but not for the react-big-calendar/lib/addons/dragAndDrop "sub-package" it has in itself.
The above gets me working import BigCalendar from 'react-big-calendar' which is great, and I want to also get working import withDragAndDrop from 'react-big-calendar/lib/addons/dragAndDrop' so I figured I'd just declare module my way there.
I cannot place the declare module statement in any TSX file, because it has to be in its own file which is not an ES module, but it also cannot be an import+export free TS file, because I am also using CRA which enforces isolatedModules and so disallows non-module TS/X files.
I can and should place it in a .d.ts file, like this:
declare module 'react-big-calendar/lib/addons/dragAndDrop' {
function withDragAndDrop(calendar: any): any;
export = withDragAndDrop;
}
This looks fine, but is not much of an improvement typing-wise. The function I am looking to type basically takes a React component and returns it with some extra props. But even to just type is as a function which takes the specific BigCalendar component and returns it is a problem, because I cannot use an import statement (to pull in the component type) in the d.ts file. If I do, it turns into a module file and that breaks the declare module statement.
I am looking for something like this:
declare module 'react-big-calendar/lib/addons/dragAndDrop' {
function withDragAndDrop(calendar: BigCalendar): typeof BigCalendar & {
props: {
extraProp1: string;
// …
extraPropN: string;
}
};
export = withDragAndDrop;
}
With that I should be able to use the HOC like this: const DragAndDropCalendar = withDragAndDrop(BigCalendar); followed by <DragAndDropCalendar originalProp={value} extraProp1={value} />.
The thing that is missing is pulling in the types to the .d.ts file in a way which doesn't turn it into a module breaking the declare module statement stripping me of types, bringing me to square one again.
What options do I have there? I tried to use require but that returns any and I couldn't figure out if <reference is the right tool here or not.
I figured out how to import the original component types (React Big Calendar in this case, but the solution is generic) in the typings (which in this case are for the RBC drag and drop addon).
withDragAndDrop.d.ts:
declare module 'react-big-calendar/lib/addons/dragAndDrop' {
import BigCalendar, { BigCalendarProps, Event } from 'react-big-calendar';
type withDragAndDropProps<TEvent> = {
onEventDrop: (args: { event: TEvent, start: stringOrDate, end: stringOrDate, allDay: boolean }) => void;
onEventResize: (args: { event: TEvent, start: stringOrDate, end: stringOrDate, allDay: boolean }) => void;
};
declare class DragAndDropCalendar<TEvent extends Event = Event, TResource extends object = object>
extends React.Component<BigCalendarProps<TEvent, TResource> & withDragAndDropProps<TEvent>>, {}
function withDragAndDrop(calendar: typeof BigCalendar): typeof DragAndDropCalendar;
export = withDragAndDrop;
};
Usage:
import withDragAndDrop from 'react-big-calendar/lib/addons/dragAndDrop';
import "react-big-calendar/lib/addons/dragAndDrop/styles.css";
const DragAndDropCalendar = withDragAndDrop(BigCalendar);
// TSX:
<DragAndDropCalendar<MyEvent> … onEventDrop onEventResize />
Flow allows you to use the following syntax to import types:
// SomeClass.js
export default class SomeClass {}
// SomeFile.js
import type SomeClass from './SomeClass';
What's the benefit of using import type instead of import? Does it tell Flow more information and let it perform better static analysis?
For the specific case of classes, it is either example will work. The key thing is that it breaks down like this:
import type ... from imports a Flow type
import ... from imports a standard JS value, and the type of that value.
A JS class produces a value, but Flowtype also interprets a class declaration as a type declaration, so it is both.
So where is import type important?
If the thing you're importing doesn't have a value, using a value import will in some cases be interpreted as an error, because most JS tooling doesn't know that Flow exists.
export type Foo = { prop: number }; for instance can only be imported with import type { Foo } from ..., since there is no value named Foo
If the thing you're importing has a JS value, but all you want is the type
Importing only the type can make code more readable, because it is clear from the imports that only the type is used, so nothing in the file could for instance, create a new instance of that class.
Sometimes importing only the type will allow you to avoid dependency cycles in your files. Depending on how code is written, it can sometimes matter what order things are imported in. Since import type ... only influences typechecking, and not runtime behavior, you can import a type without actually requiring the imported file to execute, avoiding potential cycles.
As stated at this link
With import type, you want to import the type of the class and not really the class itself.
An example given at the same link as below
// #flow
// Post-transformation lint error: Unused variable 'URI'
import URI from "URI";
// But if you delete the require you get a Flow error:
// identifier URI - Unknown global name
module.exports = function(x: URI): URI {
return x;
}
Due to we imported URI into this component, linter will check if we have used the class in this component. However, we only using it as a flow type checking and hence linter will throwing an error saying we imported unused variable.
I created a new React Native project using react-native init and in the generated template, the main component class looks like this:
export default class App extends Component<{}> {
...
}
I don't really understand what the <{}> part means. I've never seen this before and all the examples seem to omit it. Just curious as to what its purpose is and if it's necessary.
When you are using typescript, you have to specify the type of values to be expected. This allows detecting mismatching properties during compile time and reduces the amount of errors.
So when you do Component<{}>, {} is the type for Props, your component will receive.
This is how React's Component class looks like:
If you notice, the type is <P, S>, which stands for <Props, State>.
There is another interface called ComponentClass that has a signature <P>,
which initializes a new component internally with state as any. This interface is used in ReactElement's type:
So all in all, you are defining a Component which accepts no props and but can have state of any type. This is usually done when you are not sure about you component's interactions.
Ideally a component should look like this:
interface IComponentState {
...
}
interface IComponentProps {
...
}
export class MyComponent<IComponentProps, IComponentState> extends React.Component {
...
}
This enforces consumer to pass any necessary properties and enforces you to have proper value of state.
This is either Typescript of Flow. You usually don't describe props as propTypes, but rather as interface or type. Then the type is passed to React.Component as a generic.
Type of props would be the passed type plus { children?: ReactNode }
Actually there are two generic arguments, second for State
Very useful and convenient stuff.
https://www.typescriptlang.org/docs/handbook/generics.html
https://www.typescriptlang.org/docs/handbook/react-&-webpack.html
These are flow type annotations. See https://flow.org/
You'll notice that there's a #flow comment at the top of the file, and a .flowconfig file in the root of the project.
Using flow here is optional, but it can help you catch bugs.
I'm getting the following error:
src/models/priceAdjustment.ts(55,2): error TS2345: Argument of type '{ as: string; foreignKey: string; gql: any; }' is not assignable to parameter of type 'AssociationOptionsHasMany'.
Object literal may only specify known properties, and 'gql' does not exist in type 'AssociationOptionsHasMany'.
The error is obvious, I'm trying to assign a "gql" key which doesn't exist (I'm attaching it on so I can access it later on in my code somewhere else).
What I'd like to do, is extend the model.hasMany interface here, and add my own key. Is this possible? If so, how do I?
My current workaround is to cast the object to <any> before passing it in, which feels like a dirty workaround.
I have attempted the following:
import Sequelize from 'sequelize';
export declare module Sequelize {
export interface MyAssociationOptionsHasMany extends Sequelize.AssociationOptionsHasMany {
gql?: any
}
export interface MyAssociations extends Sequelize.Associations {
hasMany(target: Sequelize.Model<any, any>, options?: MyAssociationOptionsHasMany): void;
}
}
And now I get the following error:
TypeError: Cannot read property '239' of undefined
at getDeclarationSpaces (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:33121:54)
at checkExportsOnMergedDeclarations (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:33067:41)
at checkModuleDeclaration (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:35036:17)
at checkSourceElement (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:35505:28)
at Object.forEach (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:275:30)
at checkSourceFileWorker (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:35566:20)
at checkSourceFile (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:35551:13)
at getDiagnosticsWorker (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:35604:17)
at Object.getDiagnostics (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:35593:24)
at C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:56188:85
You can do this via Declaration Merging. The best way I have found this to work is by importing the library into an "extension" file and extending the library from there. For instance,
extensions/library-name.extension.ts:
import AssociationOptionsHasMany from '/path/to/definition';
import 'library-name';
declare module 'library-name' {
interface InterfaceToExtend {
hasMany(options: AssociationOptionsHasMany): ReturnType;
}
}
In the above example, we want to extend an Interface created in the library-name module. Since we are using imports, TypeScript automatically creates a module for the file, so we have to tell TypeScript that we want to extend the interface in the library-name module, not the module created for this file. To do that, we simply declare the module using the same name as the library we imported.
After telling TypeScript in which module the interface should be extended, we just use Declaration Merging by defining an interface with the same name as the interface that should be extended and adding whatever we need to it. In this case, that would be a new method that takes as an argument the specific type we want to pass it.
I have also had to update the tsconfig.json file so the extensions would be loaded first. You can do that by including the extensions directory before the rest of the app:
{
/* Blah, settings, blah */
includes: ['./app/extensions/*.ts', './app/**/*.ts'],
/* Blah, settings, blah */
}
If you're using Sublime, it might complain still, but actually compiling the code or exiting out of Sublime and reopening it seems to work just fine.
A word of warning. Declaration Merging is powerful, but can result in unexpected behavior. In general, try to avoid extending components you (or someone else) have defined elsewhere. If may be a sign that you are using these components incorrectly or the need for refactoring. Remember, the benefit of TypeScript is strict(er) typing.