Flow not crashing when passing empty object to a type - reactjs

I got this definition:
export type Entry = {|
id: string,
title: string,
|};
...
export type Props = {
entries: Array<Entry>,
...
// The following is not crashing
const rowGetter = ({ index }): Entry => props.entries[index] || {};
Using flow-bin#0.67.1, the above code is not crashing. I'd have expected for it to crash, as the definition states the return value is an Entry type, not an empty object.
Of course if I change the definition of the arrow function as follows, it crashes as expected, saying that inexact literal doesn't match, and that properties Id and title are missing:
const rowGetter = ({ index }): Entry => ({});
Is this a bug, or am I missing something here?

This is Flow's design regarding potentially undefined array elements.
Any index into props.entries is considered to resolve to an Entry, and not undefined in the case of the index being out of bounds (or a sparse array).
As a result the || {} is not considered a viable branch and is therefore not evaluated. In your refactoring, you've removed the array access and {} is correctly reported as being invalid for Entry.
If you change our method to:
const rowGetter = ({ index }): Entry => {
if (typeof props.entries[index] !== 'undefined') {
return props.entries[index]
}
return {}
}
Then flow will correctly report the return {}.

Related

Constraint on return and error to make sure one is defined

I have a simple case, hook function that returns {data, error} For simplicity lets assume that both are strings. While both error and data can be undefined when the second is returned, It's not possible that both are undefined together.
Do I have a way in typescript to set constraint that it will never happen that both are undefined? I use function in the React component and I would like this code
const myComponent: React.FC= function(){
const { data, error } = useMyCustomHook();
if ( error ) return<div> error </div>
if (data) return <div> data </div>
}
to be ok. Instead it throws
Type 'Element | undefined' is not assignable to type 'ReactElement<any, any> | null'.
Which wouldnt be here if there would be a way to tell typescript that error || data is always true.
One solution would be to make two interfaces one that require error with optional dataanother that require data leaving error optional and then type useMyCustomHook return to be union of those two, but it sounds like a bad solution that doesnt work in the scale. Imagin what will happen if I would like to have one property out of five, or even two out of five to be defined.
It would be useful to see what type useMyCustomHook returns.
If data can be falsy, such as undefined, the current code will miss one condition. In this case, you should handle the case where data is falsy.
You can modify the code like this:
const MyComponent: React.FC = () => {
const { data, error } = useMyCustomHook();
if (error) return <div>error</div>;
if (!data) return <div>empty</div>;
// you can safely access to the data
return <div>data</div>;
};
Alternatively, you can use a regular function as a component, so as not to be restricted to the React.FC type. This should still be a valid component:
const MyComponent = () => {
const { data, error } = useMyCustomHook();
if (error) return <div>error</div>;
if (data) return <div>data</div>;
}

Why is encumbranceByType object possible undefined?

I have a react component that takes in data of type EncumbranceData[] and restructures that data before mapping through it. This restructuring involves creating an empty object of type EncumbrancesByType and creating keys and values for it based on encumbranceData
However, I keep getting an "Object possible undefined" error where it says encumbrancesByType[e.type].push(e.suite). I don't understand how this could be undefined, as none of the fields on these types are optional. This component will always receive data of type EncumbranceData that will always have a type, id, and suite. EncumbranceData cannot be undefined. The object starts as an empty one, but each iteration through the forEach method should initialize each key with a truthy empty array.
How can the object possible be undefined, then, at the push(e.suite) statement?
I can get it to work by just using a bunch of optional operators (encumbrancesByType?.push()), but I feel like that defeats the point of type safety.
type EncumbranceData = {
id: string;
suite: string;
type: string;
};
interface EncumbrancesByType {
[key: string]: string[];
}
type EncumbranceAlertByTypeProps = {
id: string;
suites: string[];
type: string;
};
const EncumbranceAlertByType: React.FC<EncumbranceAlertByTypeProps> = ({ id, suites, type }) => {
const renderSuites = suites.map((s) => <span>{s}</span>);
return (
<div>
<div>{type}</div>
{renderSuites}
</div>
);
};
type ConflictingEncumbrancesAlertProps = {
encumbranceData: EncumbranceData[];
isOpen: boolean;
onContinue(): void;
onCancel(): void;
suiteIdsToCheck: string[];
encumbranceTypesToConsider?: EncumbranceType[];
};
const ConflictingEncumbrancesAlert: React.FC<ConflictingEncumbrancesAlertProps> = ({
encumbranceData,
isOpen,
onContinue,
onCancel,
suiteIdsToCheck,
encumbranceTypesToConsider,
}) => {
const encumbrancesByType: EncumbrancesByType = {}
encumbranceData.forEach((e) => {
if (!encumbrancesByType[e.type]) encumbrancesByType[e.type] = [e.suite]
else encumbrancesByType[e.type].push(e.suite)
})
const encumbrancesContent = Object.keys(encumbrancesByType).map((type) => (
<EncumbranceAlertByType suites={encumbrancesByType[type]} type={type} />
));
return <div>{encumbrancesContent}</div>;
};
export default ConflictingEncumbrancesAlert;
You likely have the noUncheckedIndexedAccess rule enable in your tsconfig. When you have this rule the compiler will always complain on unchecked index access.
Also, TS won't narrow down (remove the undefined) on index access. In order to have the compiler do that, you'll have to use an intermidiate variable.
encumbranceData.forEach((e) => {
const foo = encumbrancesByType[e.type]; // intermediate variable
if (foo) {
foo.push(e.suite); // ok
}
});
Playgroud

Array.prototype.map() expects a return value from arrow function

How to prevent this error, I am trying what ever I can, not possible to add return inside map.
Error:
Line 32:26: Array.prototype.map() expects a return value from arrow function array-callback-return
const parseChartData = (field: string, data: string[][]) => {
let chartData = [[field, 'count']];
data.map((item: any) => {
chartData.push([item._id, item._count])
});
return chartData;
};
If I understand correctly, you want to convert your data into pairs of [id, count], prepended by a header pair [field, 'count']?
Leaving type aside, you could:
Convert data into chartData with map
Prepend the header with array.unshift()
const chartData = data.map((item) =>
[item._id, item._count]
);
chartData.unshift([field, 'count']);
As for types, make sure your parameter typing is consistent with how you use it:
data parameter is declared as string[][]
each of its items (hence a string[]) is used as item._id, item._count, i.e. we try accessing its _id and _count properties, which are not supposed to exist on string[]
Hence the very probable reason why you had to type each item as any.
If your data is actually an array of objects with _id and _count properties, then simply type it ad such:
data: Array<{ _id: string; _count: number }>
if you don't want to map return data,Why not use forEach?please look up mdn docs
const parseChartData = (field: string, data: string[][]) => {
let chartData = [[field, 'count']];
data.forEach((item: any) => {
chartData.push([item._id, item._count])
});
return chartData;
};

Why does TypeScript not throw an error when a variable with wrong type is passed to useState?

Why does the TypeScript not throw an error when a variable is passed to useState? Is there a way I can still type check when passing a variable?
type BuildingNames = [
'farm',
'sawmill',
];
type BuildingType = { [n in BuildingNames[number]]?: number };
interface ITownContext {
buildings: BuildingType;
setBuildings: (buildings: BuildingType) => void;
}
interface TownProviderProps {
children: ReactNode;
}
const defaultValues = {
resourceBuildings: {
farm: 1,
asdfasdf: 5,
},
};
const TownContext = createContext<ITownContext>({} as ITownContext);
const TownProvider = ({ children }: TownProviderProps) => {
// no error
const [buildings, setBuildings] =
useState<BuildingType>(defaultValues.buildings);
// error occurs
const [buildingsDirect, setBuildingsDirect] =
useState<BuildingType>({ asdf: 1 });
return (
<TownContext.Provider
value={{
buildings,
setBuildings,
}}
>
{children}
</TownContext.Provider>
);
};
export { TownContext, TownProvider };
The difference you see between the 2 usages of useState is the effect of TypeScript excess property check, which triggers only when doing an assignment of an object literal (i.e. as opposed to assigning the reference of another variable, like defaultValues.buildings).
The fact that excess property check is not enforced when assigning the reference of another variable, enables a classic OOP usage of passing a subtype (here an object with more properties than strictly necessary).
Still, even when excess property check does not kick in, TS still checks for other errors: should one of the properties not match its type, it will be reported.
const defaultValues = {
buildings: {
farm: "string instead of number",
asdfasdf: 5,
},
};
useState<BuildingType>(defaultValues.buildings) // Types of property 'farm' are incompatible. Type 'string' is not assignable to type 'number'.
// ~~~~~~~~~~~~~~~~~~~~~~~
Playground Link

Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{ service: string; }

const [serviceList, setServiceList] = useState([{ service: '' }]);
const handleServiceChange = (e:any, index: any) => {
const { name, value } = e.target;
const list = { ...serviceList };
list[index][name] = value;//getting the error here
setServiceList(list);
}
This error appears can anyone help me understand the issue and tell me how to solve this in typescript?
You get this error because name can be anything, but elements in your list only have property service. So nothing can guarantee that name value is actually "service". In general, you should try to avoid using any to type your variables.
Check the following code. I have used ChangeEvent<HTMLInputElement> as an example, but in your case in might be different. In any case, doing this makes typescript know that name is a string.
const handleServiceChange = (e: ChangeEvent<HTMLInputElement>, index: number) => {
const {name, value} = e.target
// NOTE the syntax to copy an array!!
const list = [...serviceList];
// TS knows that `name` is a string, but it can still be any string.
// This ensures we only continue if `name === 'service'` and thus makes the error go away
if (name !== 'service') return;
list[index][name] = value; // Now we are sure that name == "service"
setServiceList(list);
}

Resources