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.
Related
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?
I am looking for a way to have a function take two arguments - a react functional component, and the correct prop for that component. The catch is that any component can be passed in and therefore any component could have its own interface. My goal is that the second argument is type aware based on the first arg.
What I have so far.
interface ComponentAProps { hello: string }
interface ComponentBProps { world: string }
interface someFunction{ (component: React.FC<any>, props: ???) => void }
// Two components as an example.
const ComponentA = ({ hello }: ComponentAProps) => return <p>hello</p>
const ComponentB = ({ world }: ComponentAProps) => return <p>world</p>
// This function takes in the component, and an object representing the props
const someFunction: someFunction = (ComponentA, { hello: 'hello' }) => void
const someFunction: someFunction = (ComponentB, { world: 'world' }) => void
// This should give a type error
const someFunction: someFunction = (ComponentB, { hello: 'hello' }) => console.log(props)
I believe I could do something like
props: <ComponentAProps | ComponentBProps>
but the issue is there could be a Component C,D,E...Z components.
Essentially this function should validate that the 2nd argument passed in are the props of whatever component is in arg 1
At runtime, there is no type information. What you can do is create some kind of schema for your objects, and then validate them against that schema. One of the most popular packages for that is ajv
To solve the core issue of making the second arg validate the props of the component we can use typescript Genrics
Instead of Validating the props based on the component passed in, we can validate the props by telling the function what props to expect. How it would implement in the OP code
interface ComponentAProps { hello: string }
interface ComponentBProps { world: string }
// This says says that a type can be passed through to the second arg
function someFunction<Type>(component: React.FC<any>, props: Type) {
return void
}
// Two components as an example.
const ComponentA = ({ hello }: ComponentAProps) => return <p>hello</p>
const ComponentB = ({ world }: ComponentAProps) => return <p>world</p>
// These will not produce type Error
someFunction<ComponentAProps>(ComponentA, { hello: 'hello' })
someFunction<ComponentBProps>(ComponentB, { world: 'world' })
// This will give a type error because we are telling the function
// that the 2 arg needs to be of type ComponentBProps
someFunction<ComponentBProps>(ComponentA, { hello: 'hello' })
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
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;
});
};
}
I'm playing around with React Native and lodash's debounce.
Using the following code only make it work like a delay and not a debounce.
<Input
onChangeText={(text) => {
_.debounce(()=> console.log("debouncing"), 2000)()
}
/>
I want the console to log debounce only once if I enter an input like "foo". Right now it logs "debounce" 3 times.
Debounce function should be defined somewhere outside of render method since it has to refer to the same instance of the function every time you call it as oppose to creating a new instance like it's happening now when you put it in the onChangeText handler function.
The most common place to define a debounce function is right on the component's object. Here's an example:
class MyComponent extends React.Component {
constructor() {
this.onChangeTextDelayed = _.debounce(this.onChangeText, 2000);
}
onChangeText(text) {
console.log("debouncing");
}
render() {
return <Input onChangeText={this.onChangeTextDelayed} />
}
}
2019: Use the 'useCallback' react hook
After trying many different approaches, I found using 'useCallback' to be the simplest and most efficient at solving the multiple calls problem.
As per the Hooks API documentation, "useCallback returns a memorized version of the callback that only changes if one of the dependencies has changed."
Passing an empty array as a dependency makes sure the callback is called only once. Here's a simple implementation.
import React, { useCallback } from "react";
import { debounce } from "lodash";
const handler = useCallback(debounce(someFunction, 2000), []);
const onChange = (event) => {
// perform any event related action here
handler();
};
Hope this helps!
Updated 2021
As other answers already stated, the debounce function reference must be created once and by calling the same reference to denounce the relevant function (i.e. changeTextDebounced in my example).
First things first import
import {debounce} from 'lodash';
For Class Component
class SomeClassComponent extends React.Component {
componentDidMount = () => {
this.changeTextDebouncer = debounce(this.changeTextDebounced, 500);
}
changeTextDebounced = (text) => {
console.log("debounced");
}
render = () => {
return <Input onChangeText={this.changeTextDebouncer} />;
}
}
For Functional Component
const SomeFnComponent = () => {
const changeTextDebouncer = useCallback(debounce(changeTextDebounced, 500), []);
const changeTextDebounced = (text) => {
console.log("debounced");
}
return <Input onChangeText={changeTextDebouncer} />;
}
so i came across the same problem for textInput where my regex was being called too many times did below to avoid
const emailReg = /^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w\w+)+$/;
const debounceReg = useCallback(debounce((text: string) => {
if (emailReg.test(text)) {
setIsValidEmail(true);
} else {
setIsValidEmail(false);
}
}, 800), []);
const onChangeHandler = (text: string) => {
setEmailAddress(text);
debounceReg(text)
};
and my debounce code in utils is
function debounce<Params extends any[]>(
f: (...args: Params) => any,
delay: number,
): (...args: Params) => void {
let timer: NodeJS.Timeout;
return (...args: Params) => {
clearTimeout(timer);
timer = setTimeout(() => {
f(...args);
}, delay);
};
}