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.
Related
In Vue 3, I'm creating a function that will accept an instance of a component and props to pass through. I'm also using TypeScript and was wondering if I can type those parameters. For example, the function would be something like:
const example = (component, props) => {
//
};
So my questions would be:
How can I specify a type for a component instance? These are not always going to be the same component, but would at least be components that are used for a similar purpose.
Would there be a way for me to specify the type for the props and confine it to the props that would be for the first parameter (the component)?
You could use many feature provided by typescript and the Component type from vue to achieve your proper typing, make a generic type that extends the Component then infers the component options/props using infer, use Partial to make them optional :
import type { Component } from "vue";
function example<T extends Component>
(Comp: T, props: T extends Component<infer P> ? Partial<P> : never) {
//....
}
example(Alert, { variant: "success"})
Note: this also infers the attributes and component instance utilities
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).
Using the connect function from redux seems to make my IDE (PhpStorm) lose the ability to "Find Usages" on my classes. I assume because connect returns any, so import SomeClass from SomeClass.ts loses the type information from that file.
export default connect(mapStateToProps)(SomeClass);
From the redux docs:
It does not modify the component class passed to it; instead, it returns a new, connected component class for you to use.
But I want my IDE to treat this as if it was my original class/component so it has all the type information.
One way I found how to fix this is by using annotations with #connect, but this requires me to put mapStateToProps & mapDispatchToProps functions above the class. I prefer to keep my class files fairly clean with the class at the very top of the file.
This gives Block-scoped variable 'mapSateToProps' used before its declaration
import { connect } from 'react-redux'
#connect(mapStateToProps)
class TestClass {
}
const mapStateToProps = (state, props) => {
}
Is there a way for me to use connect, keep the class type information in my IDE, and have mapStateToProps either below the class or inside the class as static functions? Or just anywhere else in the same file?
I'm using TypeScript & Babel.
You could write mapStateToProps as a function instead, since when using the function keyword the declaration is hoisted
e.g.
function mapStateToProps(state: TypeOfYourRootStore, props: TypeOfYourComponentsProps) {}
If I have an ES6 component like so:
component OldThing extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div>{this.props.someProp}</div>;
}
}
an ES6 container like so:
const OldThingContainer = connect(mapStateToProps, mapDispatchToProps)(OldThing);
and a new typescript component like so:
component NewThing extends React.Component<Props, State> {
render() {
return <OldThingContainer someProp={someValue} />
}
}
I currently get an IntrinsicAttributes error about someProp not existing on OldThing. How do I get rid of that error without adding types/typedefs to/for OldThing and/or OldThingContainer? I tried the #augments solution on the OldThingContainer where it's defined but that doesn't seem to help. Neither did adding the comment on the component itself.
The reason I want to be able to import JS without types is that we are adding typescript to a very large, multiple years old code base and it's currently difficult to get people to write typescript components at all, let alone if they also have to type out every existing component their component wants to import.
Is there an existing elegant solution to this problem or am I going to have to go through ahead of everyone and manually type every existing JS component (hundreds) in some typedef file to get rid of/prevent these errors?
I tried your example and it seemed some issue with the type connect inferred for your component.
One quick'n'dirty fix would be to annotate your JS component with:
/**
* #type {React.ComponentType<any>}
*/
const OldThingContainer = connect(mapStateToProps, mapDispatchToProps)(
OldThing
);
One more elegant fix (but maybe not possible) would be trying to dive into the react-redux typings. The connect function requires you to supply some type arguments for it to properly work. When not supplied, those arguments are given {}, not any (because any can silently eat your types).
Comment: From the few I've worked with react+redux+typescript, lib typedefs are one of the biggest pain points. React typedefs can get really complicated. (flow just will hang up your editor with "waiting for flow server", which is even worse). As TS+Redux gets more popular and more support is added, this may get a lot better in a few months...
I have some use cases for inheritance and decoration (as in Decorator pattern) of components and directives in Angular 2.
The example is component with basic template that doesn't suit the case, to the point when it easier to define a new template instead of modifying the DOM of existing one programmatically. The rest of component metadata should be inherited.
Basically it is
export const BASE_SOME_COMPONENT_METADATA = { ... };
#Component(BASE_SOME_COMPONENT_METADATA);
export class BaseSomeComponent { ... }
...
import { BaseSomeComponent, BASE_SOME_COMPONENT_METADATA } from '...';
#Component(Object.assign({}, BASE_SOME_COMPONENT_METADATA, { template: '...' });
export class SomeComponent extends BaseSomeComponent {}
And more complicated case is
#Component({ ... });
export class ThirdPartyComponent {
#Input() ...;
#Input() ...;
#Input() ...;
...
}
...
import { ThirdPartyComponent as BaseThirdPartyComponent } from '...';
#Component({
// may modify or replace any of the original properties
template: ...,
styles: ...
...
});
export class ThirdPartyComponent extends BaseThirdPartyComponent {}
Notice that ThirdPartyComponent has numerous inputs. There may be cases when it is beneficial to modify a component internally instead of wrapping it and modifying its behaviour from the outside. A wrapper component that enumerates them all in template and transfers them to ThirdPartyComponent may be WET and dirty:
<third-party inputs="inputs" that="that" should="should" be="be" enumerated="enumerated">
Extra layout elements that are introduced by wrapper components may be prohibited in some cases.
ThirdPartyComponent may be core component (a button) that is used by other third-party components. Then they should be affected too, so it may be necessary to 'decorate the decorator' all over the injector, not just extend it.
In Angular 1.x thirdPartyDirective is a service that gives full access to component DDOs, so they could be decorated, extended, deep fried, etc. What are direct counterparts to this approach in Angular 2? If this breaks some rules and voids the warranty, it's ok.
How can a component/directive that doesn't export its metadata be extended?
How can the metadata of existing component be modified?
Your inputs will be inherited from the parent class automatically. Regarding properties of the Component decorator itself, there is no native way to do that in Angular2. The Angular2 team won't provide support for this: https://github.com/angular/angular/issues/7968#issuecomment-219865739.
If you really want something like that, you need to implement with a custom decorator that updates annotations...
This article could interest you:
https://medium.com/#ttemplier/angular2-decorators-and-class-inheritance-905921dbd1b7#.8r5fuys6l