UNPKG

@cotype/core

Version:

<h2 align="center"> <img src="https://cotype.dev/logo.svg" alt="cotype" /> </h2>

173 lines (140 loc) 4.21 kB
# Recepies These code examples are to specific to be part of the project itself but seem valuable in different scenarios. ## Consuming a Union-Model in Typescript+React Assuming we have the following union model configured in cotype ```ts const fooBarUnion = { type: "union", types: { foo: { type: "object", fields: { children: { type: "string" } } }, bar: { type: "object", fields: { children: { level: "number" } } } } }; ``` When consuming the API we will receive an array of objects that either have a `children` property of type `string` or a `level` property of type number. Each entry also brings it's `_type`, which we can use to [discriminate](https://basarat.gitbooks.io/typescript/content/docs/types/discriminated-unions.html). The [build-client](https://github.com/cotype/build-client) will type them as: ```ts type FooBarUnion = Array< { _type: "foo"; children: string } | { _type: "bar"; level: number } >; ``` We now want to implement react components that can render each possible type and use only the existing properties. We want to receive type errors in the following scenarios: - React component requires a prop not provided by the api - API provides a type but no component is configured to render it - API type changes but we did not update the component ### Introducing: `createUnion` ```tsx import React, { ReactElement } from "react"; type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; type Model = { _type: string; [key: string]: any; }; export type UnionHandlers<S extends Model[], R = void> = { [k in S[number]["_type"]]: ( props: Omit<Extract<S[number], { _type: k }>, "_type"> ) => R }; export default function createUnion<S extends Model[]>( comps: UnionHandlers<S, ReactElement> ) { return function Union({ children }: { children: S }) { return ( <> {children.map(({ _type, ...props }, i) => { const Comp = comps[_type]; /* We need to use any since `props` is still a union of all possible models because we have not explicitly discriminated the model. But since comps is securely typed, we know that `Comp` can handle this exact props. */ return <Comp key={i} {...props as any} />; })} </> ); }; } ``` ### Usage ```tsx import createUnion from "./createUnion"; type FooBarUnion = Array< { _type: "foo"; children: string } | { _type: "bar"; level: number } >; const FooBar = createUnion<FooBarUnion>({ foo({ children }) { return <div>{children}</div>; }, bar({ level }) { return <div>{level}</div>; } }); function Blog({ sections }: { sections: FooBarUnion }) { return <FooBar>{sections}</FooBar>; } ``` We now get errors when: - changing `_type: "foo"` to something else - removing `bar` implementation from `createUnion` parameter - passing nothing or anything else as children into `<FooBar>` - not returning a react element in any of the `createUnion` implementations - Using a parameter that is not provided in the API ### Additional Notes #### Pass reusable components to createUnion We do not need to implement the components directly in the createUnion call. We can also import a reusable component from our library and pass it ```tsx // Headline.tsx import React from "react"; type Props = { children: ReactNode; }; export default function Headline({ children }: Props) { return <h1>{children}</h1>; } ``` ```tsx import Headline from "./Headline"; /* ... */ const FooBar = createUnion<FooBarUnion>({ foo: Headline /* ... */ }); ``` #### UnionHandlers You can use `UnionHandlers` type helper for other (non-react) cases or to type-safely implement them before passing into `createUnion` ```ts import { UnionHandlers } from "./createUnion"; const handlers: UnionHandlers<FooBarUnion, any> = { foo: console.log, bar: console.error }; const sections: FooBarUnion = await getSections(); sections.forEach(section => { switch (section._type) { case "foo": return handlers.foo(section); case "bar": return handlers.bar(section); } }); ```