- 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}</>; | |
| }; |