Skip to content

Instantly share code, notes, and snippets.

@saeedseyfi
Last active May 7, 2022 09:26
Show Gist options
  • Save saeedseyfi/ee180ca9c9e49fef4e11cc8d31e6a256 to your computer and use it in GitHub Desktop.
Save saeedseyfi/ee180ca9c9e49fef4e11cc8d31e6a256 to your computer and use it in GitHub Desktop.
React TypeScript Declarative Switch Cases
import React, { useEffect, useState } from "react";
import { Switch, Case } from "./Switch";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(1);
useEffect(() => {
const inverval = setInterval(() => {
setCount((c) => (c === 5 ? 1 : c + 1));
}, 1000);
return () => {
clearInterval(inverval);
};
}, []);
return (
<>
<blockquote>
<h3>Switch with type check and type inference:</h3>
<Switch<typeof count> /* generic arg is optional */
expression={count}
default={"Default :)"}
>
{(Case) => (
<>
<Case value={1}>Case 1</Case>
<Case value={2}>Case 2</Case>
<Case value={3}>Case 3</Case>
<Case value={4}>Case 4</Case>
{/* <Case value={'invalid type'}>Case invalid</Case> */}
</>
)}
</Switch>
</blockquote>
<br />
<br />
<blockquote>
<h3>
Switch without type check and type inference, but simpler syntax:
</h3>
<Switch expression={count} default={"Default :)"}>
<Case value={1}>Case 1</Case>
<Case value={2}>Case 2</Case>
<Case value={3}>Case 3</Case>
<Case value={4}>Case 4</Case>
</Switch>
</blockquote>
</>
);
}
import React, { Fragment } from "react";
import PropTypes from 'prop-types';
export const Case = Fragment;
export const Switch = ({
expression,
children,
...rest
}) => {
const cases =
typeof children === 'function'
? children(Case).props.children
: children;
const matched = cases.find((child) => child.props.value === expression);
const fallback = rest.default || null;
return <>{matched ? matched.props.children || fallback : fallback}</>;
};
Switch.propTypes = {
expression: PropTypes.any.isRequired,
children: PropTypes.oneOfType([
PropTypes.func,
PropTypes.arrayOf(PropTypes.element),
]),
default: PropTypes.any,
}
import React, { Fragment, ReactElement, ReactNode } from "react";
type CaseProps<T> = {
children: React.ReactNode;
value: T;
};
export const Case = <T extends unknown>(props: CaseProps<T>) => {
return <Fragment {...props} />;
};
type Cases<T> = ReactElement<CaseProps<T>>[];
type RenderFn<T> = (
Case: React.FC<CaseProps<T>>
) => ReactElement<{ children: Cases<T> }>;
type SwitchProps<T> = {
expression: T;
children: RenderFn<T> | Cases<T>;
default?: ReactNode;
};
export const Switch = <T extends unknown>({
expression,
children,
...rest
}: SwitchProps<T>) => {
const cases =
typeof children === "function"
? children(Case as React.FC<CaseProps<T>>).props.children
: children;
const matched = cases.find((child) => child.props.value === expression);
const fallback = rest.default || null;
return <>{matched ? matched.props.children : fallback}</>;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment