React DND typescript support - reactjs

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> {

Related

How to deal with FluentUI React Toggle Button click event in a Typescript Class

Hi I'm quite new to Typescript/FluentUI/PCF components.
I'm creating a Toggle Button as a PowerApps pcf control and I'm struggling to understand how to implement it using an export class pattern.
See code the tsx below. The first implementation is using code lifted from the fluentui/controls documentation and works fine.
But my other pcf controls use a Class pattern, which is the second implementation in the code below, and I'd like to keep this consistent. Plus I'd like to learn how to do this. Due to my lack of experience, I don't know how to implement the onChange properly in this Class pattern so it toggles.
I think a part of my lack of knowledge is understanding this line of code, what it does and how it works.
const [muted, { toggle: setMuted }] = useBoolean(false);
Can anyone help me refactor the class so it works ?
Thanks
/* eslint-disable prettier/prettier */
import * as React from "react";
import { DefaultButton, IBaseButtonState } from "#fluentui/react/lib/Button";
import { IIconProps, Toggle } from "#fluentui/react";
import { IUseBooleanCallbacks, useBoolean } from "#fluentui/react-hooks";
export interface IButtonElementProps {
name?: string;
disabled?: boolean;
checked?: boolean;
}
//Implementation exporting a function - taken from FluidUI example
export const ButtonElement: React.FunctionComponent<IButtonElementProps> = (props) => {
const { disabled, checked } = props;
const icon: IIconProps = { iconName: "Accept" };
const [muted, { toggle: setMuted }] = useBoolean(false);
return (
<DefaultButton
toggle
iconProps={icon}
text={muted ? "Volume muted" : "Volume unmuted"}
checked={muted || checked}
onClick={setMuted}
></DefaultButton>
);
};
//Implementation exporting class
export class ButtonControl extends React.Component<IButtonElementProps, IBaseButtonState> {
constructor(props: IButtonElementProps) {
super(props);
}
icon: IIconProps = { iconName: "AddIn" };
//muted: [boolean, IUseBooleanCallbacks] = useBoolean(false); // This breaks the control
//tmuted = [useBoolean(false)]; //so does this
muted: IUseBooleanCallbacks = { // this runs but I don't know if its right or understand how to use it
toggle() {
}, setFalse() {
}, setTrue() {
},
};
public render(): React.ReactNode {
// eslint-disable-next-line prettier/prettier
return <DefaultButton
toggle
iconProps={this.icon}
text={this.props.name}
checked={true} //how do I make this so it takes the value of muted
onClick={this.clicked}
></DefaultButton>;
}
private clicked(args: React.MouseEvent<HTMLAnchorElement>) {
console.log("clicked");
console.log(args);
// is this the right place and what do I put here to set the property ?
}
}

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

What is the proper interface for element with "GetWrappedInstance"

I am writting React+Redux with typescript. I need to access the reference of one wrapped instance, like this.refs.items.getWrappedInstance(), but got typescript error Property 'getWrappedInstance' does not exist on type 'ReactInstance'
Which interface shall I give to items? Are there any interface defined by Redux that I can declare or I need to create my own?
I tried to google and search stackoverflow but cannot find an answer.
Thank you!
We got the same error using Flow and our most senior UI developer ultimately said to use // $FlowIgnore: some comment
I think that because accessing wrappedInstance is an antipattern flow and typescript may not support it...yet?
I am interested if anyone has a different answer.
I came across this issue and while I didn't find a native solution, I did manage to create one. Below is a working sample implementation with the solution.
Keep note of the { withRef: true } in the connect function.
Here is a small utility type to add the missing definition.
// connect.ts
type WrappedConnectedComponent<T> = {
getWrappedInstance(): T
}
export function unwrapConnectedComponent<T>(component: T): T | undefined {
if (component) {
const wrappedComponent: WrappedConnectedComponent<T> = component as any
if (wrappedComponent.getWrappedInstance) {
return wrappedComponent.getWrappedInstance()
}
}
return undefined
}
Here is a simple component that we'll be accessing later.
// SomeOtherComponent.tsx
import * as React from 'react'
import { connect } from 'react-redux'
class SomeOtherComponent extends React.Component {
log = () => {
console.log('logged')
}
render() {
return <div />
}
}
const mapStateToProps = () => ({})
const mapDispatchToProps = () => ({})
const ConnectedSomeOtherComponent = connect(
mapStateToProps,
mapDispatchToProps,
null,
{ withRef: true } // Important
)(SomeOtherComponent)
export default ConnectedSomeOtherComponent
export { SomeOtherComponent }
Here is the main component that does all the work.
// SomeComponent.tsx
import * as React from 'react'
import ConnectedSomeOtherComponent, { SomeOtherComponent } from './SomeOtherComponent'
import { unwrapConnectedComponent } from './connect'
class SomeComponent extends React.Component {
someOtherComponent?: SomeOtherComponent
private setSomeOtherComponent = (someOtherComponent: SomeOtherComponent) => {
this.someOtherComponent = unwrapConnectedComponent(someOtherComponent)
}
onClick = () => {
if (this.someOtherComponent) {
this.someOtherComponent.log()
}
}
render() {
return (
<div>
<button onClick={this.onClick} />
<ConnectedSomeOtherComponent ref={this.setSomeOtherComponent} />
</div>
)
}
}
export default SomeComponent

flowtype: inheritance and generic

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 () { /*...*/ }
}

Resources