Generating curried constructor functions from io-ts type definition - fp-ts

How can I implement function from that satisfies condition below?
import * as t from 'io-ts';
declare const from: <P extends t.Props, T extends t.TypeC<P>, R = t.TypeOf<T>>(
_: T
) => { readonly [K in keyof R]: (_: Omit<R, K>) => (_: R[K]) => R };
const Lorem = t.type({
foo: t.string,
bar: t.number,
baz: t.boolean,
});
type Lorem = t.TypeOf<typeof Lorem>;
const expectedLorem: Lorem = { foo: 'foo', bar: 42, baz: true };
const LoremFrom = from(Lorem);
expect(LoremFrom.foo({ bar: 42, baz: true })('foo')).toStrictEqual(expectedLorem);
expect(LoremFrom.bar({ foo: 'foo', baz: true })(42)).toStrictEqual(expectedLorem);
expect(LoremFrom.baz({ foo: 'foo', bar: 42 })(true)).toStrictEqual(expectedLorem);

Related

React Component and Typescript - Generic Props

I am currently facing a problem using React and Typescript. I have a generic component that I use to build specific tables like this:
export const SpecificTable = () => {
return
<GenericTable
object1={{ ... }}
object2={{ ... }}
object3={{ ... }}
object4={{ ... }}
/>;
};
The generic component and its props look like this:
export type GenericProps<GenericType1, GenericType2, GenericType3, GenericType4> {
object1: { ... };
object2: { ... };
object3: { ... };
object4: { ... };
}
export const GenericTable = <
GenericType1 extends ...,
GenericType2 extends ...,
GenericType3 extends ...,
GenericType4 extends ...
>(
props: GenericProps<GenericType1, GenericType2, GenericType3, GenericType4>
) => {
// build the table
}
The problem here is that whenever I need to use this generic component to render a table, the generic types are going to change for each different table. The GenericTable types should look something like this, I suppose :
export const GenericTable = <
GenericType1 extends SpecificType1,
GenericType2 extends SpecificType2,
GenericType3 extends SpecificType3,
GenericType4 extends SpecificType4
>(
props: GenericProps<GenericType1, GenericType2, GenericType3, GenericType4>
) => {
// build the table
}
However, since I can not hard code the specific types there, what should I do? How can I pass in these specific types as props to the generic component?
From your description, it may not be necessary for GenericTable to be generic, but it may need to be.
Here's what I do:
Define just the minimum information that GenericTable needs to do its work for each of those props.
Define a type alias (type = ... or interface { ... }) for those minimum types (optional, but convenient).
Determine whether GenericTable needs to be generic. If it just needs to accept the props you've listed, it doesn't, because you can just use the minimum-necessary types from above. But sometimes, a component needs to call a callback you pass to it and pass back something it receives, and in that case if you want the callback strongly typed, you may want to make GenericTable generic.
Note that the decision may not be the same for all of the props. It's entirely possible one or two of them need to be generic but the others don't.
So for instance:
type MinType1 = /*...*/;
type MinType2 = /*...*/;
type MinType3 = /*...*/;
type MinType4 = /*...*/;
Then, if GenericTable doens't have to be generic:
type GenericTableProps = {
object1: MinType1,
object2: MinType2,
object3: MinType3,
object4: MinType4,
};
const GenericTable = ({object1, object2, object3, object4}: GenericTableProps) => {
// ...
};
Or if it does:
type GenericTableProps<
Type1 extends MinType1,
Type2 extends MinType2,
Type3 extends MinType3,
Type4 extends MinType4,
> = {
object1: Type1,
object2: Type2,
object3: Type3,
object4: Type4,
};
const GenericTable = <
T1 extends MinType1,
T2 extends MinType2,
T3 extends MinType3,
T4 extends MinType4,
> = ({object1, object2, object3, object4}: GenericTableProps<T1, T2, T3, T4>) => {
// ...
};
The key thing is #1 above: defining the minimum information GenericTable needs to do its job, then using that as the constraint for the relevant props.
Here's an example with just one type parameter which is used by two out of the three props on the component:
import React from "react";
type GenericTableRowType = {
id: string | number;
text: string;
};
type GenericTableProps<RowType extends GenericTableRowType> = {
label: string;
rows: RowType[];
onRowClick: (row: RowType) => void;
};
const GenericTable = <RowType extends GenericTableRowType>({label, rows, onRowClick}: GenericTableProps<RowType>) => {
return <table>
<thead>
<tr>
<th>{label}</th>
</tr>
</thead>
<tbody>
{rows.map((row) => <tr key={row.id} onClick={() => onRowClick(row)}>
<td>{row.text}</td>
</tr>)}
</tbody>
</table>
};
// Example use:
type MyRowType = {
id: number;
text: string;
flag: boolean;
}
const Example = () => {
const rows: MyRowType[] = [
{id: 1, text: "one", flag: true},
{id: 2, text: "two", flag: false},
{id: 3, text: "three", flag: false},
];
const onRowClick = (row: MyRowType) => {
console.log(`Row ${row.id}, flag = ${row.flag}`);
}
return <div>
<GenericTable label="Example" rows={rows} onRowClick={onRowClick} />
</div>;
};
Playground link, and here's a live example with the types commented out:
/*
type GenericTableRowType = {
id: string | number;
text: string;
};
type GenericTableProps<RowType extends GenericTableRowType> = {
label: string;
rows: RowType[];
onRowClick: (row: RowType) => void;
};
*/
const GenericTable = /*<RowType extends GenericTableRowType>*/({label, rows, onRowClick}/*: GenericTableProps<RowType>*/) => {
return <table>
<thead>
<tr>
<th>{label}</th>
</tr>
</thead>
<tbody>
{rows.map((row) => <tr key={row.id} onClick={() => onRowClick(row)}>
<td>{row.text}</td>
</tr>)}
</tbody>
</table>
};
// Example use:
/*
type MyRowType = {
id: number;
text: string;
flag: boolean;
}
*/
const Example = () => {
const rows/*: MyRowType[]*/ = [
{id: 1, text: "one", flag: true},
{id: 2, text: "two", flag: false},
{id: 3, text: "three", flag: false},
];
const onRowClick = (row/*: MyRowType*/) => {
console.log(`Row ${row.id}, flag = ${row.flag}`);
}
return <div>
<GenericTable label="Example" rows={rows} onRowClick={onRowClick} />
</div>;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Obviously, that's a silly component (a table with one column), but it serves to demonstrate the concepts.

Typescript property does not exist on type

I am receiving this error for both list.item references:
client_1 | TS2339: Property 'items' does not exist on type 'List'.
Here is some relevant code. How do I work around this without setting it to any?
export interface List {
createdAt: number;
id: string;
items: Array<ToDo>;
updatedAt: number;
}
const emptyList = { createdAt: 0, id: '', items: [], updatedAt: 0 }
...
const [list, setList] = useState<List>(emptyList)
...
const filterList = <List extends any>(list: List) => {
if (activeFilter === 'All') {
return setFilteredTodos(list.items)
}
setFilteredTodos(list.items.filter((todo: ToDo) => todo.completed === filterMap[activeFilter as keyof object] as {}))
}
Writing <List extends any> before the function defines a generic type parameter with the (new) name List on the arrow function filterList.
To make it more clear, your code is equivalent to:
const filterList = <T extends any>(list: T) => { ... }
As you probably want to use the type List from above, you should remove the <List extends any> from the function assigned to filterList:
const filterList = (list: List) => { ... }

Polymorphic React component has incorrect type inferences in IDE

declare const MyComponent = <A extends {id: bigint|number}>(props: MyProps<A>) => React.FC<{}>
interface MyProps<A extends {id: number}> {
data: A[]
orderBy: keyof A
}
declare const x: { id: number, foo: string }[]
const Foo = () => {
<MyComponent data={x} orderBy="foo"/> // <-- Type 'keyof A' is not assignable to type 'never'
}
I think this is because TS is failing to infer from data that type is of type { id: number, ... } so thus it thinks data is of type {} and thus keyof A is of type never (and so orderBy cannot be anything at all).
So my question is how can I fix this other than // #ts-ignore right above? Can I manually specify the type of A somehow in my TSX? I can't do anything like
import { MyComponent } from ...
declare const specify = <A>() => typeof MyComponent<A> // <-- error here because of the <A> part after MyComponent
const Foo = <A ...>() => {
const SpecialMyComponent = specify<A>()
return <SpecialMyComponent .../>
}
It seems you can't do those generics inside your TSX without TS thinking it's TSX instead of specifying the value of a generic.
I've provided explanatory comments in the code below. Feel free to reply if something's not clear:
TS Playground
import {default as React} from 'react';
// bigint was missing from this union in MyProps:
type ID = {
id: bigint | number;
};
type MyProps <A extends ID> = {
data: A[];
orderBy: keyof A;
};
// Suggestion: use React.ReactElement if you're rendering JSX or a component that does:
declare function MyComponent <A extends ID>(props: MyProps<A>): React.ReactElement;
declare const x: { id: number; foo: string; }[];
const Foo = () => {
<MyComponent data={x} orderBy="foo"/> // ok
}
// And, finally... yes, you CAN use generics with TSX!
type Y = { id: bigint; baz: string[]; };
declare const y: Y[];
const Bar = () => {
<MyComponent<Y> data={y} orderBy="baz" /> // ok
};

React testing with Typescript: Passing array of objects as props

I am trying to pass an array of objects to mock component data for testing like so:
const mockPackage = {
id: '1232-1234-12321-12321',
name: 'Mock Package',
price: 8.32,
description: 'Mock description',
globalProduct: {
imageUrl: 'http://imageurl.com',
},
};
const mockPackageData = {
name: 'Package',
packages: [mockPackage],
};
beforeEach(() => {
component = render(
<SuiteContextProvider>
<PackageCard
showDetail={{ display: true, selectedIndex: 1, hideOthers: false }}
handleShowDetail={handleShowDetail}
packageData={mockPackageData}
/>
</SuiteContextProvider>,
);
});
However, I receive the following error:
The component that receives the data destructures the packageData like so:
export interface Package {
id: string;
name: string;
price: number;
description: string;
globalProduct: {
imageUrl: string;
};
}
export interface PackageData {
name: string;
packages: [];
}
type Props = {
packageData: PackageData;
handleShowDetail: (data: DefaultPackageProps) => void;
showDetail: {
display: boolean;
selectedIndex: number | null;
hideOthers: boolean;
};
};
const PackageCard: React.FC<Props> = ({ packageData, handleShowDetail, showDetail }: Props) => {
return (
<>
{packageData.packages.map((packageInfo: Package, index: number) => {
const {
id,
name,
price,
description,
globalProduct: { imageUrl },
} = packageInfo;
Your PackageData defintion should be
export interface PackageData {
name: string;
packages: Package[];
}
You current code packages: []; declares packages to must be an empty array that is why you get the type '0' error.

Getting onChange value to be of generic type without type assertion in TypeScript

I'v created a generic react component with option elements like this:
import * as React from "react";
export interface Option {
value: string;
label: string;
disabled?: boolean;
}
export interface SelectProps<CustomOption extends Option> {
options: CustomOption[];
onChange: (event: React.ChangeEvent<HTMLSelectElement>) => void;
selectedValue: string;
}
export const Select: React.FunctionComponent<SelectProps<Option>> = (props): JSX.Element => {
const options = props.options.map(o => {
return (
<option key={o.value} value={o.value} disabled={o.disabled}>
{o.label}
</option>
);
});
return (
<select onChange={props.onChange} value={props.selectedValue}>
{options}
</select>
);
};
I'm using this like this:
type OptionValues = "FOO" | "BAR" | "BAZ" | "NO_ANSWER";
const createSelect = (selectedOption: OptionValues, onChange: (v: OptionValues) => void): JSX.Element => {
const changeHandler = (event: React.ChangeEvent<HTMLSelectElement>): void => onChange(event.currentTarget.value as OptionValues);
interface SelectOptions extends Option {
value: OptionValues;
}
const selectProps: SelectProps<SelectOptions> = {
onChange: changeHandler,
selectedValue: selectedOption,
options: [
{
value: "FOO",
label: "foo"
},
{
value: "BAR",
label: "bar"
},
{
value: "BAZ",
label: "baz"
},
{
value: "NO_ANSWER",
label: " -- ",
disabled: true
}
]
};
return <Select {...selectProps} />;
};
Is there any way to avoid using type assertion in the changeHandler.
event.currentTarget.value as OptionValues. Also I see that the whole createSelect suddenly is very verbose with references to OptionValues everywhere, but I guess there is no way around it. Alternative ways to solve the problem is also welcomed. That is making sure value is of predetermined types.
In your case you actually need a type-guard, as typescript is not that smart.
If your Select component deep down, is just what I think it is, it will never work.
See typeguards
type OptionValues = 'FOO' | 'BAR' | 'BAZ' | 'NO_ANSWER'
const isOptionValue = (value: unknown): value is OptionValues =>
typeof value === 'string' && ['FOO', 'BAR', 'BAZ', 'NO_ANSWER'].includes(value)
const createSelect = (selectedOption: OptionValues, onChange: (v: OptionValues) => void): JSX.Element => {
const changeHandler = (event: React.ChangeEvent<HTMLSelectElement>): void => {
const value = event.currentTarget.value
if(isOptionValue(value))
onChange(value);
}
...

Resources