refining flow union type - javascript

Please see the code below. I have to do two casting to avoid any flow error. If I use the commented out lines instead, it complains.
playground
/* #flow */
import * as React from "react";
type ConfObj = { label: string };
type Conf = React.Node | ConfObj;
type MyComponentProp = {
confs: Array<Conf>,
}
export default function MyComponent({
confs = [],
}: MyComponentProp) {
const items = confs.map((item, idx) => {
if (React.isValidElement(item)) {
// return React.cloneElement(item, {
return React.cloneElement(((item: any): React.Element<*>), {
key: idx.toString(),
});
}
const item2 = ((item: any): ConfObj);
return <span>{item2.label}</span>;
// return <span>{item.label}</span>;
});
return <div>items</div>
}
Is there a better way to do this to avoid the casting. Is there a better way to write isValidElement, so flow can deduce the type once the if condition matches. For example, if it is a valid react element, why do I need to cast it? or if it not, why accessing label gives error?

An item is of type Conf (which is Node | ConfObj)
When you enter the if statement, flow doesn't know for sure that item is a valid Element<*> (this could be known by flow I think tho), so you have to explicitly typecast it.
The <span>{item.label}</span> has the same problem. You also have to explicitly typecast it to a ConfObj, because a Node doesn't have a label attribute.

Related

Restrict string value input from Node Express request query?

export type TodoQuery = {
sortBy?: 'createdAt' | undefined;
}
export const extractTodoQuery = (reqQuery: ParsedQs): TodoQuery => {
return {
sortBy: reqQuery.sortBy as 'createdAt' | undefined,
};
}
The reqQuery from extractTodoQuery function above comes from req.query in a node express app. I am trying to limit the sortBy property to certain string values or it should be undefined. Currently sortBy can still have different values in the return object based on the request query parameter. What would be an elegant way to achieve this in typescript?
You likely will want some kind of helper method to validate and return a strongly typed value. The cast doesn't force any conversion on its own and requires some validation logic.
There are many ways you could achieve this, but here is one approach by first defining an array of accepted values. Then, a helper that verifies sortBy is a string and is one of the values. If it's not a string or not one of the valid values, then it filters it out and returns undefined.
interface ParsedQs { [key: string]: undefined | string | string[] | ParsedQs | ParsedQs[] }
// Valid sorting options. Change this as you please.
// The `as const` assertion ensures only these values are valid (not any `string`).
const SORT_BY = ['createdAt', 'updatedAt', 'name'] as const;
export type TodoQuery = {
// Infer the type based on the array so as you add/remove values the
// types automatically update.
sortBy?: typeof SORT_BY[number] | undefined;
}
// Helper to do the actual validation and return a strongly typed value.
const parseSortBy = (res: ParsedQs) => {
if (typeof res.sortBy === "string") {
return SORT_BY.find(valid => valid === res.sortBy)
}
return undefined;
}
export const extractTodoQuery = (reqQuery: ParsedQs): TodoQuery => {
return {
sortBy: parseSortBy(reqQuery),
};
}
TypeScript playground link.

Why is the Non-null assertion operator "!" needed? [duplicate]

TFlavour is a discriminatory union, which is then a value of an Object.
Trying to implement this, works in js, but ts gets angry.
ts playground link
Expected:
ts to understand discriminated unions in loops,
as it understands it without loops
type TFlavour = ({
natural: true,
naturalComponent : string
}) | ({
natural: false,
artificialComponent: string
})
type TIceCream = Record<string, TFlavour>
const IceCreams: TIceCream = {
CokeIceCream: {
natural:false,
artificialComponent: 'Coke'
},
Vanilla: {
natural: true,
naturalComponent: 'Vanilla Extract'
},
Mango: {
natural: true,
naturalComponent: 'Mango Fruit'
}
}
const iceCreamKeys = Object.keys(IceCreams)
iceCreamKeys.forEach( item => {
if(IceCreams[item].natural){
console.log(IceCreams[item].naturalComponent) // ts says "Property doesn't exists.."
}
})
if(IceCreams.Mango.natural){
console.log(IceCreams.Mango.naturalComponent) // Works here
}
The problem is that the compiler doesn't know how to do narrowing on an object property like IceCreams[item] where you are indexing with a key whose type isn't known to be a specific literal type. TypeScript is only following the type of the index, not the identity. And the type of item is string. If you have item1 and item2, both of type string, then checking IceCreams[item1] wouldn't let you conclude anything about IceCreams[item2], right? And since TypeScript can't tell the difference between item1 vs item2 or item vs item, it can't narrow. This is a known limitation of TypeScript reported at microsoft/TypeScript#10530. Maybe someday it will be addressed. But for now, there's an easy workaround:
Just copy the value into a new variable, so that the problematic indexing occurs only once:
iceCreamKeys.forEach(item => {
const x = IceCreams[item];
if (x.natural) {
console.log(x.naturalComponent) // okay
}
})
Playground link to code
Instead of directly accessing the item with the index, try to store it in a separate variable. This way, TypeScript will recognize the right type:
iceCreamKeys.forEach( item => {
const c = IceCreams[item]
if(c.natural){
console.log(c.naturalComponent)
}
})
(working TS Playground)
iceCreamKeys.forEach( item => {
if(IceCreams.item.natural){
console.log(IceCreams.item.naturalComponent) // Accessing it like this worked
}
})
Just found out, this works too.

React children with typescript

I am having trouble with setting proper types for react children.
export const recursiveCloneChildren = (
children: React.ReactNode,
handleChildChange,
disableContent: boolean,
) => {
return React.Children.map(children, (child) => {
if (!isObject(child)) {
return child;
}
let childProps = {
...child.props,
disabled: child.props.disabled || disableContent,
};
const requiredOrValidatableChildProps = {
...childProps,
checkValidationState: handleChildChange,
};
if (child.props.required || child.props.validatable) {
childProps = {
...requiredOrValidatableChildProps,
};
}
if (child.props.children) {
childProps.children = recursiveCloneChildren(child.props.children, handleChildChange, disableContent);
}
return React.cloneElement(child, childProps);
});
};
I am getting this error
Property 'props' does not exist on type '{} | ReactElement<any, string
| JSXElementConstructor> | ReactNodeArray | ReactPortal'.
Property 'props' does not exist on type '{}'.
I tried to set types for child directly ( React.ReactChild ), and showed another error on children.
How can it be solved?
I don't really like what you're trying to do, because if I found it in a codebase I'd be really confused.
But to answer your question. The child argument in the function passed to Children.map is of type ReactNode. This is the most general type, meaning you can end up with pretty much anything that can be a valid child. I actually don't know if your code is going to work but considering you want to access props, I'm assuming what you want is to make sure you're operating on ReactElement. In that case, your isObject check is not exhaustive enough. What you need is a type guard.
So the quick and dirty option is to write your own type guard and something like this is actually enough:
function isReactElement(child: React.ReactNode): child is React.ReactElement {
return isObject(child) && 'props' in child;
}
and then instead of
if (!isObject(child)) {
return child;
}
you just do
if (!isReactElement(child)) {
return child;
}
and it works. However probably a better idea is to use react-is library which is an official, Facebook-maintained library and it's widely used in many React libraries and it does basically exactly what you want but the checks are significantly better than what I proposed above. In your case you'd want to use isElement, the same way I showed above. Just make sure to install #types/react-is, too.

typescript cast number type to never [duplicate]

Code is:
const foo = (foo: string) => {
const result = []
result.push(foo)
}
I get the following TS error:
[ts] Argument of type 'string' is not assignable to parameter of type 'never'.
What am I doing wrong? Is this a bug?
All you have to do is define your result as a string array, like the following:
const result : string[] = [];
Without defining the array type, it by default will be never. So when you tried to add a string to it, it was a type mismatch, and so it threw the error you saw.
Another way is:
const result: any[] = [];
This seems to be some strange behavior in typescript that they are stuck with for legacy reasons. If you have the code:
const result = []
Usually it would be treated as if you wrote:
const result:any[] = []
however, if you have both noImplicitAny FALSE, AND strictNullChecks TRUE in your tsconfig, it is treated as:
const result:never[] = []
This behavior defies all logic, IMHO. Turning on null checks changes the entry types of an array?? And then turning on noImplicitAny actually restores the use of any without any warnings??
When you truly have an array of any, you shouldn't need to indicate it with extra code.
I got the same error in React function component, using useState hook.
The solution was to declare the type of useState at initialisation using angle brackets:
// Example: type of useState is an array of string
const [items , setItems] = useState<string[]>([]);
I was having same error In ReactJS statless function while using ReactJs Hook
useState.
I wanted to set state of an object array , so if I use the following way
const [items , setItems] = useState([]);
and update the state like this:
const item = { id : new Date().getTime() , text : 'New Text' };
setItems([ item , ...items ]);
I was getting error:
Argument of type '{ id: number; text: any }' is not assignable to parameter of type 'never'
but if do it like this,
const [items , setItems] = useState([{}]);
Error is gone but there is an item at 0 index which don't have any data(don't want that).
so the solution I found is:
const [items , setItems] = useState([] as any);
const foo = (foo: string) => {
const result: string[] = []
result.push(foo)
}
You needed specify what the array is since result = [] has a return type of any[]. Typically you want to avoid any types since they are meant to be used as an "Escape hatch" according to Microsoft.
The result is an object that is an array of string.
The solution i found was
const [files, setFiles] = useState([] as any);
I was able to get past this by using the Array keyword instead of empty brackets:
const enhancers: Array<any> = [];
Use:
if (typeof devToolsExtension === 'function') {
enhancers.push(devToolsExtension())
}
Error: Argument of type 'any' is not assignable to parameter of type 'never'.
In tsconfig.json -
"noImplicitReturns": false,
"strictNullChecks":false,
Solution: type as 'never'
Remove "strictNullChecks": true from "compilerOptions" or set it to false in the tsconfig.json file of your Ng app. These errors will go away like anything and your app would compile successfully.
Disclaimer: This is just a workaround. This error appears only when the null checks are not handled properly which in any case is not a good way to get things done.
I got the error when defining (initialising) an array as follows:
let mainMenu: menuObjectInterface[] | [] = [];
The code I got the problem in:
let mainMenu: menuObjectInterface[] | [] = [];
dbresult.rows.forEach((m) => {
if (!mainMenu.find((e) => e.menucode === m.menucode)) {
// Not found in mainMenu, yet
mainMenu.push({menucode: m.menucode, menudescription: m.menudescription}) // Here the error
}
})
The error was: TS2322: Type 'any' is not assignable to type 'never'
The reason was that the array was initialised with also the option of an empty array.
Typescript saw a push to a type which also can be empty. Hence the error.
Changing the line to this fixed the error:
let mainMenu: menuObjectInterface[] = [];
You need to type result to an array of string const result: string[] = [];.
One more reason for the error.
if you are exporting after wrapping component with connect()() then props may give typescript errorSolution: I didn't explore much as I had the option of replacing connect function with useSelector hook
for example
/* Comp.tsx */
interface IComp {
a: number
}
const Comp = ({a}:IComp) => <div>{a}</div>
/* **
below line is culprit, you are exporting default the return
value of Connect and there is no types added to that return
value of that connect()(Comp)
** */
export default connect()(Comp)
--
/* App.tsx */
const App = () => {
/** below line gives same error
[ts] Argument of type 'number' is not assignable to
parameter of type 'never' */
return <Comp a={3} />
}
you could also add as string[]
const foo = (foo: string) => {
const result = []
(result as string[]).push(foo)
}
I did it when it was part of an object
let complexObj = {
arrData : [],
anotherKey: anotherValue
...
}
(arrData as string[]).push('text')
in latest versions of angular, you have to define the type of the variables:
if it is a string, you must do like that:
public message : string ="";
if it is a number:
public n : number=0;
if a table of string:
public tab: string[] = [];
if a table of number:
public tab: number[]=[];
if a mixed table:
public tab: any[] = [];
.......etc (for other type of variables)
if you don't define type of variable: by default the type is never
NB: in your case, you must know the type of variables that your table must contain, and choose the right option (like option 3 ,4 5 ).
This error occurs when you set the value of a property in an object when you haven't set the properties of the object. Declare a type for the object with its properties, then assign the type when you instantiated the object. Now you can set the values of the properties without this error.
I had this error for useRef
before with error:
const revealRefs = useRef([]);
const addToRefs = (el) => {
if (el && !revealRefs.current.includes(el)) {
revealRefs.current.push(el);
}
};
after without error:
const revealRefs = useRef<HTMLElement[]>([]);
const addToRefs = (el: HTMLElement | null) => {
if (el && !revealRefs.current.includes(el)) {
revealRefs.current.push(el);
}
};

Import flowtype disjoint union?

Using flowtype on a current react / redux project.
I define in my actions.js file a disjoint union type:
export type ArticleAction =
{ type: 'ARTICLE_SET_EDITION' }
| { type: 'ARTICLE_BLABLA', blip: string };
And then in my reducer I have
import type { ArticleAction } from './actions';
[...]
const articlesReducer = (state: any = initialState, action: ArticleAction): any => {
if (action.type === 'ARTICLE_BLABLA') {
const test = action.blip.shoups;
return test;
}
}
Flow does not detect a problem.
But! if I declare ArticleAction directly in reducer.js, it does recognize that action.blip.shoups is invalid because blip is a string.
Any idea about what I am doing wrong ?
thx
TL;DR Flow doesn't error in situations like this today, but most likely will in the future.
This doesn't have anything to do with the import/exports or even the union type, you could simplify it all the way down to this:
function method(val: 'foo') {
if (val === 'bar') {
// unreachable...
}
}
Flow can see that it is a impossible refinement and could know that the inner code is unreachable. However, Flow does not error in unreachable scenarios. Today it simply marks the value of val as an "empty" type in that code path and moves on.
We have started to lay the groundwork for this reachability analysis and will use it to create errors in future versions of Flow.
We can also use reachability analysis to test exhaustiveness, i.e.:
function method(val: 'foo' | 'bar') {
if (val === 'foo') {
// ...
} else if (val === 'bar') {
// ...
} else {
// possibilities of val have been exhausted, this is unreachable...
}
}
These are common requests and we are working on them.

Categories

Resources