Flow Array Generic is Incompatible with Itself? - javascript

I'm trying to create an es6 class with flow type checking:
/* #flow */
export default class ListHolder<Tv> {
getList = (): Array<Tv> => {
return [];
};
iterateOverList = (): mixed => {
return this.getList().map((item: mixed, index: number) => {
// no-op
return null;
});
};
}
The problem is that I keep getting a flow error that Tv [1] is incompatible with Tv [1]. referencing the line getList = (): Array<Tv> => {. Can someone help me understand this error?
It resolves if I comment out the map invocation, but I haven't been able to make much progress beyond that and the error messages aren't particularly helpful.
Thanks,

Looks like your creating a class, but assigning methods to a class is not done using assignment operator. = and arrow functions =>.
I think this is more what your after. ->
/* #flow */
export default class ListHolder<Tv> {
getList (): Array<Tv> {
return [];
};
iterateOverList (): mixed {
return this.getList().map((item: mixed, index: number) => {
// no-op
return null;
});
};
}

Related

How to export functions from a custom hook

The basic structure of my hook:
const useMyHook = () => {
const func1 = (): boolean => { ... }
const func2 = (type: "type1" | "type2"): boolean => { ... }
const func3 = (): boolean => { ... }
return {
func1, func2, func3
}
}
export default useMyHook
I'm currently using it like this, which works in dev, but breaks my build.
import useMyHook from "../hooks/useMyHook";
const { func1 } = useMyHook()
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const isValid = func1();
if (isValid) {
// do more things
}
}
return (
<form>...</form>
)
When running yarn build, I am faced with this error:
Objects are not valid as a React child (found: object with keys {func1, func2, func3}). If you meant to render a collection of children, use an array instead.
My attempt at solving this was to export my custom hook functions in an array, as the error seemed to suggest.
return [
func1, func2, func3
]
And using it like this:
const [ func1 ] = useMyHook();
const isValid = func1();
TypeScript is not happy with this:
An argument for 'type' was not provided.
But this is confusing, because func1 does not accept a type argument as func2 does.
Questions
Is there some way I work around this build-time error and export the { } of functions? This feels like the cleanest approach.
If there is no way to achieve 1, how can I properly use the exported array of functions without my types being misread?

Cannot call `setTimeout` because function [1] requires another argument

I'm getting a flow (incompatible-call) error when setting someProp to a setTimeout. The error states that I cannot call setTimeout because function [1] requires another argument. I'm not sure what this means, and I need help! The code worked before I tried to "flow" it. TIA!
// #flow
import React, { Component } from 'react';
type Props = {
someProp: TimeoutID,
};
export default class MyClass extends Component<Props> {
someProp: ?TimeoutID;
resetState = (i: number) => {
this.setState({ ... });
delete this.someProp;
};
onMouseLeave = () => {
this.someProp = setTimeout(this.resetState, 300); // <- error
};
render() {}
}

Cannot access react state from callback

I have the following components:
const ParentComponent: React.FC = () => {
// Somewhere in the code, I set this to some value
const [newType, setNewType] = useState<any>(undefined);
// Somewhere in the code, I set this to true
const [enableAddEdge, setEnableAddEdge] = useState(false);
const addEdge = (data: any) => {
console.log("newType", newType); // This is undefined
}
return (
...
<VisNetwork
newType={newType}
onAddEdge={addEdge}
enableAddEdge={enableAddEdge}
/>
...
)
}
export default ParentComponent;
interface Props {
newType: any;
onAddEdge: (data: any) => void;
enableAddEdge: boolean;
}
const VisNetwork: React.FC<Props> = (props: Props) => {
const options: any = {
// Some vis-network specific option configuration
manipulation: {
addEdge: (data: any, callback: any) => props.onAddEdge(data);
}
}
...
// Some code to create the network and pass in the options
const network = new vis.Network(container, networkData, options);
useEffect(() => {
if (props.enableAddEdge) {
// This confirms that indeed newType has value
console.log("newType from addEdge", props.newType);
// Call reference to network (I name it currentNetwork)
// This will enable the adding of edge in the network.
// When the user is done adding the edge,
// the `addEdge` method in the `options.manipulation` will be called.
currentNetwork.addEdgeMode();
}
}, [props.enableAddEdge])
useEffect(() => {
if (props.newType) {
// This is just to confirm that indeed I am setting the newType value
console.log("newType from visNetwork", props.newType); // This has value
}
}, [props.newType]);
}
export default VisNetwork;
When the addEdge method is called, the newType state becomes undefined. I know about the bind but I don't know if it's possible to use it and how to use it in a functional component. Please advise on how to obtain the newType value.
Also, from VisNetwork, I want to access networkData state from inside options.manipulation.addEdge. I know it's not possible but any workaround? I also need to access the networkData at this point.
You need to useRef in this scenario. It appears const network = new vis.Network(container, networkData, options); uses the options from the first render only. Or something similar is going on.
It's likely to do with there being a closure around newType in the addEdge function. So it has stale values: https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function
In order to combat this, you need to useRef to store the latest value of newType. The reference is mutable, so it can always store the current value of newType without re-rendering.
// Somewhere in the code, I set this to some value
const [newType, setNewType] = useState<any>(undefined);
const newTypeRef = useRef(newType);
useEffect(() => {
// Ensure the ref is always at the latest value
newTypeRef.current = newType;
}, [newType])
const addEdge = (data: any) => {
console.log("newType", newTypeRef.current); // This is undefined
}

Typescript | Warning about Missing Return Type of function, ESLint

I have a REACT-STATELESS-COMPONENT, in a project with TypeScript. It has an error, saying, that
Missing return type on function.eslint(#typescript-eslint/explicit-function-return-type)
I am not sure what it wants me to do. Here is my code:
import React, { Fragment} from 'react';
import IProp from 'dto/IProp';
export interface Props {
prop?: IProp;
}
const Component = <T extends object>({ prop }: Props & T) => (
<Fragment>
{prop? (
<Fragment>
Some Component content..
</Fragment>
) : null}
</Fragment>
);
LicenseInfo.defaultProps: {};
export default Component;
Can you tell me what I need to do. I need to read about TS, but currently I don't get it at all. And I can't commit right now cause of this.
I'd recommend using the types provided by react; they'll include the return type. If you're on the version 16.8.0 or later of react, do this:
const Component: React.FunctionComponent<Props> = (props) => (
Or use the shorthand:
const Component: React.FC<Props> = (props) => (
Prior to 16.8, you'd instead do:
const Component: React.SFC<Props> = (props) => (
Where SFC stands for "stateless functional component". They changed the name, since function components are no longer necessarily stateless.
For a function return type it goes after the arguments:
({ prop }: Props & T): JSX.Element => {}
JSX.Element is what TypeScript will infer, it's a pretty safe bet.
If you're curious, you should be able to see what TypeScript infers as the return type by hovering over Component, this will show the entire signature.
If you use #types/react you don't have to provide return types for React components. You can disable this rule for react components like this. Just add this to your .eslintrc.js:
overrides: [
{
files: ['*.jsx', '*.tsx'],
rules: {
'#typescript-eslint/explicit-module-boundary-types': ['off'],
},
},
],
This is how I usually declare a component using typescript:
import * as React from 'react';
type MyComponentProps = {
myStringProp: String,
myOtherStringProp: String
};
const MyComponent: React.FunctionComponent<MyComponentProps> = ({ myStringProp, myOtherStringProp }): JSX.Element => {
return (
<div>
<h1>This is My Component</h1>
</div>
);
};
export default MyComponent;
This rule aims to ensure that the values returned from functions are of the expected type.
The following patterns are considered warnings:
Problem :
// Should indicate that no value is returned (void)
function test() {
return;
}
// Should indicate that a number is returned
var fn = function () {
return 1;
};
// Should indicate that a string is returned
var arrowFn = () => 'test';
class Test {
// Should indicate that no value is returned (void)
method() {
return;
}
}
solution :
// No return value should be expected (void)
function test(): void {
return;
}
// A return value of type number
var fn = function (): number {
return 1;
};
// A return value of type string
var arrowFn = (): string => 'test';
class Test {
// No return value should be expected (void)
method(): void {
return;
}
}
Link : https://github.com/typescript-eslint/typescript-eslint/blob/v4.22.0/packages/eslint-plugin/docs/rules/explicit-function-return-type.md

Flow generics in react component props

I'm trying to type a component's props with generics but flow doesn't catch the error. Basically, I want to have a component with two props fetch and cb. Whatever type fetch returns, should be the return type of cb.
// #flow
import React, { Component } from 'react';
type Props<T> = {
fetch: () => Promise<T>,
cb: T => T,
};
class Client<T> extends Component<Props<T>> {
componentDidMount() {
const { fetch, cb } = this.props;
fetch().then(cb);
}
render() {
return (<div />)
}
}
const App = () => (
<Client
fetch={ () => Promise.resolve(1) }
cb={ n => 'sfd' } />
);
At the bottom, I'm passing a function to fetch that resolves to number but the cb is returning a string and there is no flow error. Why?
Here is the repl.
This is not even a react thing. A simple function will fail checking the type:
const fun = <T>(a: () => T, b: T => T): T => b(a());
fun(() => 2, n => 'qwe');
In case of function you are wrong. It should be assign to a variable:
function f<T>(a: () => T, b: T => T): T {
return b(a());
}
const r: number = f(() => 2, n => ""); // error
try it
But in case of a component props... maybe it expect to be parameterized, but I can`t find a way to parameterize a component.
As James Kraus said on a comment, flow is inferring the type of your T to be
number|string. There is nothing preventing a generic to become a sum type, and unless you restrict one of the functions or parametrise your generic function with a concrete type, flow will continue to infer the sum type.
Very close to what Kirill wrote, but maybe easier to understand where the restriction is coming from is this:
function f<T>(a: () => T, b: T => T): T {
return b(a());
}
const r = f<number>(() => 2, n => "");
Here you will get an error in the second function because we already told flow
This generic must be a number, enforce it!
Then it does its job.

Categories