flowtype: inheritance and generic - reactjs

I want to inherit from a React component, and define new Props. Something like that (got a lot of errors with Bar, should completely be wrong):
// #flow
class Component<Props> {
props: Props;
constructor(props: Props) {
this.props = props;
}
}
type FooProps = {
x: number;
}
class Foo extends Component<FooProps> {
_render(value: string) {
return `hello, ${value}`;
}
render() {
return this._render(`${this.props.x}`);
}
};
type BarProps = {
x: number;
y: number;
}
class Bar extends Foo<BarProps> {
render() {
return this._render(`${this.props.x} ${this.props.y}`);
}
}
const foo: Foo = new Foo({x: 1});
const bar: Bar = new Bar({x: 1, y: 2});
How should I use flow generics with inheritance? (in the context of React components, if it matters).
Using flow 0.57.2 and react 16.0.0.

the simplest case, creating a new react component and typing the props would look like this in ES:
// #flow
import React from 'react'
type Props = { books: Array<String>, search: (string) => void }
class SearchList extends React.Component {
static defaultProps: Props
props: Props
render () { /*...*/ }
}
SearchList.defaultProps = { books: [], search: (_) => undefined }
EDIT: forgot to mention this is using the class fields proposal that is in stage 3. If you are using a bootstrapper like create-react-app you can use it. Otherwise you can do:
// #flow
import React from 'react'
type Props = { books: Array<String>, search: (string) => void }
class SearchList extends React.Component<Props> {
render () { /*...*/ }
}

Related

how do I declare additional property for React class component?

I rewrite my React Project with TypeScript. now I know how to declare property interface and state interface for a Class Component, simple like this:
export interface ComponentProps {
propName: string
}
interface ComponentState {
stateName: number
}
class myComponent extends React.Component<ComponentProps, ComponentState> {
...
}
but error accur when I do something in componentDidMount() lifecycle:
componentDidMount() {
this.customProperty = 26; // here I could save an id returned by setInterval to cancel it in the componentWillUnmount() for example.
}
[ts] Property 'customProperty' does not exist on type 'MyComponent'. [2339]
What can I do to declare additional property correctly, not just simple silence the error.
I've learned the basic of typescript.
import React, { Component } from 'react';
export interface CheckoutProps {
total: number;
customer: string;
}
interface State {
isChecking: boolean;
}
class Checkout extends Component<CheckoutProps, State> {
state = {
isChecking: false
};
componentDidMount() {
this.customProperty = 'sean';
}
render() {
return <div>hello</div>;
}
}
export default Checkout;
Class level properties can be defined after the class declartion,
They can be initialized either at the time of declartion or in constructor.
Change your Class To :-
class Checkout extends Component<CheckoutProps, State> {
customProperty:string=''; //Here,you can define your class level properties
state = {
isChecking: false
};
componentDidMount() {
this.customProperty = 'sean';
}
render() {
return <div>hello</div>;
}
}
Hope this helps,
Cheers !!
You can declare it within the class:
class myComponent extends React.Component<ComponentProps, ComponentState> {
private customProperty: string = '';
componentDidMount() {
this.customProperty = 'sean';
}
}

How To Declare TypeScript Static Method From React Functional Component

I'm needing to reference static methods from my functional component in React. I've made a small example here of what I want to do (it works in JavaScript). The error I'm getting is on line 10 const x = ...
TS2339: Property 'GetMyArray' does not exist on type
'FunctionComponent'.
import React, {FunctionComponent} from 'react';
interface Props {
isLoading?: boolean,
}
const Speakers : FunctionComponent<Props> = ({isLoading}) => {
if (isLoading) {
const x = Speakers.GetMyArray();
return (
<div>{JSON.stringify({x})}</div>
);
} else {
return <div></div>;
}
};
Speakers.GetMyArray = () => {
return [1,2,3,4]
};
export default Speakers
This would be easier to do by using a class component since the static functions and property types can be inferred in class definitions.
class Speakers extends React.Component<Props> {
static GetMyArray = () => [1, 2, 3, 4]
render() {
const { isLoading } = this.props
if (isLoading) {
const x = Speakers.GetMyArray(); // works great
return (
<div>{JSON.stringify({x})}</div>
);
} else {
return <div></div>;
}
}
}
That said, you could do it extending React.SFC or using an intersection type:
const Speakers: React.SFC<Props> & { GetMyArray?: () => number[]; } = (props) => {
const { isLoading } = props
if (isLoading) {
const x = Speakers.GetMyArray!(); // works, sorta
return (
<div>{JSON.stringify({x})}</div>
);
} else {
return <div></div>;
}
}
You'll have to mark GetMyArray as optional because you cannot define it at the same time as you define the function, so you'll have to use a ! operator (or check that the function exists) when calling the static function. The ! in Speakers.GetMyArray!(); tells the type checker that you know what you're doing and that GetMyArray is not indeed undefined. See here to read about the non-null assertion operator
Update
I did not see that using React.FC is now the preferred way to use function components since function components can no longer be considered stateless.
If you can get away with only using const Speakers = (props: Props) => ... then upgrading TypeScript to 3.1 might be your best bet!
My answer is not exact to your question. But I think will be helpful for someone who wants to add a static component to the function component.
import React from 'react';
import { Text } from 'react-native';
import Group, { RadioButtonGroupProps } from './Group';
type RadioButtonType = {
text: string,
};
interface StaticComponents {
Group?: React.FC<RadioButtonGroupProps>
}
const RadioButton: React.FC<RadioButtonType> & StaticComponents =
({ text }) => (<Text>{text}</Text>);
RadioButton.Group = Group;
export default RadioButton;
Remember config like this if you want to import React from 'react' directly instead of import * React from 'react'
Achievement:
You can write an interface that extends React.FC with your custom static method, then assign that interface to your functional component.
// Extends FC with custom static methods
interface ComponentWithStaticMethod<TProps> extends React.FC<TProps> {
staticMethod: (value: string) => void;
}
// Your component with the interface
const Component: ComponentWithStaticMethod<{}> = () => {
// Your component goes here...
return null;
}
// Declare your static method for your functional component
Component.staticMethod = (value: string): void => {
console.log(value);
}
You should have it working since TS version 3.1 (see section "Properties declarations on functions")
Main Answer
For future googlers out there, now it works as intended:
interface Props {
isLoading?: boolean,
}
interface StaticFields {
staticField: number,
staticFunctionField: () => void,
}
export const Component: FC<Props> & StaticFields = (props) => {}
Component.staticField = 42;
Component.staticFunctionField = () => {}
Special Case: memo and forwardRef
In such cases, you won't be able to define typings like in above. Using Object.assign can solve the problem:
import { memo } from 'react'
interface Props {
isLoading?: boolean,
}
const Component = memo((props: Props) => {})
export const ComponentWithStaticFields = Object.assign({}, Component, {
staticField: 42,
staticFunctionField: () => {},
})

How can I return a string value from a component that is a React Context Consumer

I have a React component that is a consumer of a Context Provider. I am using this for a translation component for internationalization. My consumer is basically a wrapper around the React Context Consumer element and calls an underlying translate function. My problem is, I want to just return the STRING value from this component, but it actually returns a React element which breaks my interfaces. Is it possible to "unwrap" this and just give me the string?
import * as React from 'react';
import {IIntlContext, IntlContext} from 'Common/Intl/IntlProvider';
export default (key: string) => {
// I JUST WANT THE STRING!
return (
<IntlContext.Consumer>
{
(context: IIntlContext) => {
return context.translate(key);
}
}
</IntlContext.Consumer>
);
};
The provider wraps my whole React app and looks like this:
import * as React from 'react';
interface IIntlProviderProps {
locale: string;
i18n: any;
children: React.ReactNode;
}
interface IIntlProviderState {
locale: string;
i18n: any;
}
export interface IIntlContext extends IIntlProviderState {
translate: (key: string) => any;
}
export const IntlContext = React.createContext<IIntlContext | undefined>(
undefined
);
export class IntlProvider extends React.Component<IIntlProviderProps, IIntlProviderState> {
constructor(props: IIntlProviderProps) {
super(props);
this.state = {
locale: props.locale,
i18n: props.i18n
};
}
public translate = (key: string) => {
return this.state.i18n.t(key);
};
public render() {
const context: IIntlContext = {
...this.state,
translate: this.translate
};
return (
<IntlContext.Provider value={context}>
{this.props.children}
</IntlContext.Provider>
);
}
}
export default IntlProvider;
My goal is to just be able to import the consumer and pass it a string to translate like this:
import translate from 'Common/Intl/Translator';
translate('some text');

React DND typescript support

I read some users mentioning that they are using this library with Typescript support, but I cannot find any documentation anywhere nor I cannot seem to make it work on my own.
I am using typescript 2 and I can't manage to create a really simple working example that simply allows me to drag an existing component. I tried several possibilities but I always get stuck into some problems with typings either when calling DragSource (as a decorator or function) or when rendering the resulting component.
In short I would like an example that shows the usage of react-dnd in typescript that allows me how to make an existing component draggable, possibly without modifiying the component itself (it shouldn't be aware that it is draggable)
Thank you for any help!
I've got it working with the DT types package on 2.1. It doesn't compile with strictNullChecks and I haven't been able to track down the reason why. (When you decorate your components with #DragSource and #DropTarget, you somehow change the return type of the render function from Element to Element | null, but I can't see how.)
The other niggle is that all of the props that are inserted by the collecting function are undefined when you first instantiate your components in a render method, so your options are to pass a bunch of {undefined as any} or else declare your collector-injected props as optionals and typeguard them out every where you look at them. Overall the type declaration file is not bad and I found the typing to be more helpful than harmful when getting to know the library.
import {
ConnectDragSource,
DragDropContext,
DragSource,
DragSourceSpec,
DragSourceCollector,
DragSourceConnector,
DragSourceMonitor,
DragElementWrapper,
ConnectDropTarget,
DropTarget,
DropTargetConnector,
DropTargetMonitor,
ClientOffset,
DropTargetSpec } from 'react-dnd';
let HTML5Backend = require('react-dnd-html5-backend');
import { AdjacencyMatrixGraph } from "Geometry/Graph";
import * as React from "react";
import * as Sauce from "Sauce";
import * as ReactDOM from "react-dom";
import * as $ from "jquery";
import { Position2 } from "Geometry";
import * as Rx from "rxjs";
import * as Util from "Util";
require("./GraphBuilder.scss");
interface NodeProps {
label?: string;
position: ClientOffset;
}
/* New node from the node well */
export interface NodeSourceProps {
isDragging : boolean;
connectDragSource: ConnectDragSource;
}
export interface NodeSourceState {
}
// Spec: drag events to handle.
let nodeSourceSpec: DragSourceSpec<NodeSourceProps> = {
beginDrag: (props: NodeSourceProps) => ({}),
};
// Collect: Put drag state into props
let nodeSourceCollector = (connect: DragSourceConnector, monitor: DragSourceMonitor) => {
return {
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
}
};
#DragSource("new-node", nodeSourceSpec, nodeSourceCollector)
export class NodeSource extends React.Component<NodeSourceProps, NodeSourceState> {
constructor(props: NodeSourceProps) {
super(props);
}
render() {
const { connectDragSource, isDragging } = this.props;
return connectDragSource(<span className="node-source">{'\u2683'}</span>);
}
}
/* main graph area */
interface GraphCanvasProps {
connectDropTarget: ConnectDropTarget,
isOver: boolean,
graph: AdjacencyMatrixGraph<NodeProps>;
}
interface GraphCanvasState {}
const canvasDropTargetSpecification: DropTargetSpec<GraphCanvasProps> = {
drop(props: GraphCanvasProps, monitor: DropTargetMonitor, component: React.Component<GraphCanvasProps, any>) {
// console.log("Handling drop", print_monitor(monitor));
let pos = monitor.getSourceClientOffset();
if (monitor.getItemType() === "main-node-move") {
let node = (monitor.getItem() as any);
graph.setData(node.id, { position: pos });
}
else if (monitor.getItemType() === "new-node") {
graph.addNode("node-" + graph.order(), { position: pos });
}
},
};
function canvasDropTargetCollectingFunction(connect: DropTargetConnector, monitor: DropTargetMonitor) {
let rv = {
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
};
return rv;
}
/* ... here's a DropTarget ... */
#DropTarget(["main-node-move", "new-node"], canvasDropTargetSpecification, canvasDropTargetCollectingFunction)
export class GraphCanvas extends React.Component<GraphCanvasProps, GraphCanvasState> {
constructor(props: GraphCanvasProps) {
super(props);
}
render(): JSX.Element | null {
const { connectDropTarget, graph } = this.props;
let nodes = graph.nodes();
let nodeEls = Object.keys(nodes).map(k => {
let node = nodes[k];
return <CanvasNode
key={k}
id={k}
node={node}
graph={graph}
connectNodeDrop={null as any}
connectMoveNodeDragger={(null)}/>
});
return connectDropTarget(<div className="graph-canvas">
{nodeEls}
</div>);
}
}
/* ... Here's a the DragContext decorator ... */
#DragDropContext(HTML5Backend)
class Markov extends React.Component<MarkovProps, MarkovState> {

React/TypeScript: extending a component with additional properties

I am trying to use react to recreate my currents components (written in pure typescript) but I can't find a way to give additional props to a component extending an other.
export interface DataTableProps {
columns: any[];
data: any[];
}
export class DataTable extends React.Component<DataTableProps, {}> {
render() {
// -- I can use this.props.columns and this.props.data --
}
}
export class AnimalTable extends DataTable {
render() {
// -- I would need to use a this.props.onClickFunction --
}
}
My problem is that I need to give AnimalTable some props that would be irrelevant to DataTable. How can I do that ?
You'll need to make DataTable generic so that you'll be able to use an interface which extends DataTableProps:
export interface AnimalTableProps extends DataTableProps {
onClickFunction: Function;
}
export class DataTable<T extends DataTableProps> extends React.Component<T, {}> { }
export class AnimalTable extends DataTable<AnimalTableProps> {
render() {
// this.props.onClickFunction should be available
}
}
as a rule of thumb it is probably better to avoid inheritance. luckily TS and react are great tools allowing that (unlike c# for example, where inheritance often saves you a bunch of boilerplate)
export interface DataTableProps {
columns: any[];
data: any[];
}
export class DataTable extends React.Component<DataTableProps, {}> {
render() {
// -- I can use this.props.columns and this.props.data --
}
}
export type AnimalTableProps = DataTableProps & {
onClickFunction: () => void;
};
export class AnimalTable extends React.Component<AnimalTableProps, {}> {
render() {
const {onClickFunction, ...tableProps} = this.props;
// use onClickFunction however you need it
return <DataTable {...tableProps}></DataTable>
}
}
The most elegant solution that I found (without extra generic class) is
interface IBaseProps {
name: string;
}
class Base<P> extends React.Component<P & IBaseProps, {}>{
}
interface IChildProps extends IBaseProps {
id: number;
}
class Child extends Base<IChildProps> {
render(): JSX.Element {
return (
<div>
{this.props.id}
{this.props.name}
</div>
);
}
}
For those who need, base classes can declare required/abstract methods that all instances must implement:
import { Component } from 'react'
abstract class TestComponent<P = {}, S = {}, SS = any> extends Component<P, S, SS> {
abstract test(): string
}
type Props = {
first: string,
last: string,
}
type State = {
fullName: string,
}
class MyTest extends TestComponent<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
fullName: `${props.first} ${props.last}`
}
}
test() {
const { fullName } = this.state
return fullName
}
}
Complete example of creating a component that you can extend off of and maintain state and props
import { Component } from "react";
// Props for the Base Component
export interface BaseComponentProps { }
// State for the Base Component
export interface BaseComponentState {
isLoaded?: boolean
}
// The Base Component that your components can extend
export class BaseComponent<Props extends BaseComponentProps, State extends BaseComponentState, SS = any> extends Component<Props, State, SS> {
State: BaseComponentState = {
isLoaded: false
}
constructor(props: Props) {
super(props);
}
componentDidMount() {
this.setState({ isLoaded: true })
}
}
// Props for your specialized component
export interface MainComponentProps extends BaseComponentProps {
}
// State for your specialized component
export interface MainComponentState extends BaseComponentState {
CanRead: boolean
}
// Your component which now extends the BaseComponent
export class MainComponent extends BaseComponent<MainComponentProps, MainComponentState> {
state: MainComponentState = {
CanRead: false
}
componentDidMount() {
super.componentDidMount();
if (this.state.isLoaded) {
this.setState({ CanRead: true })
}
}
}

Resources