- Declarative and human-readable.
- Can infer the type of the expression and help you set valid case values.
- Small in size.
- JS and TS
Demo: https://codesandbox.io/s/declarative-jsx-switch-case-typescript-react-q7mkho?file=/src/App.tsx
Demo: https://codesandbox.io/s/declarative-jsx-switch-case-typescript-react-q7mkho?file=/src/App.tsx
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}</>; | |
}; |