A component that only shows one of its children and allows dynamic switching between children
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { | |
useContext, | |
createContext, | |
ReactNode, | |
useMemo, | |
Children, | |
isValidElement, | |
useState | |
} from 'react'; | |
interface SwitchboardContextInterface { | |
switchTo: (r: string) => void; | |
} | |
interface SwitchboardProps { | |
children: ReactNode; | |
initialId?: string; | |
} | |
type MapOfChildrenType = Map<string, ReactNode>; | |
export const SwitchboardContext = createContext<SwitchboardContextInterface | undefined>(undefined); | |
export const useSwitchboardContext = (): SwitchboardContextInterface => { | |
const context = useContext(SwitchboardContext); | |
if (context === undefined) { | |
throw new Error('❌ useSwitchboardContext must be used within a Switchboard'); | |
} | |
return { switchTo: context.switchTo }; | |
}; | |
export default function Switchboard({ children, initialId }: SwitchboardProps): JSX.Element { | |
const mapOfChildren = useMemo(() => { | |
const initialMap: MapOfChildrenType = new Map(); | |
return Children.toArray(children).reduce<MapOfChildrenType>((childrenMap, currentChild) => { | |
if (typeof currentChild === 'string' || typeof currentChild === 'number') { | |
console.error('Switchboard child is invalid'); | |
} else if (isValidElement(currentChild) && !childrenMap.has(currentChild.props.id)) { | |
childrenMap.set(currentChild.props.id, currentChild); | |
} | |
return childrenMap; | |
}, initialMap); | |
}, [children]); | |
const [firstId] = mapOfChildren.keys(); | |
const [currentId, setCurrentId] = useState(initialId || firstId); | |
const currentComponent = mapOfChildren.get(currentId); | |
const switchTo = (to: string): void => { | |
if (!mapOfChildren.has(to)) { | |
console.error(`The "to" value "${to}" does not exist in the generated map.`); | |
} | |
setCurrentId(to); | |
}; | |
return ( | |
<SwitchboardContext.Provider value={{ switchTo }}> | |
{currentComponent} | |
</SwitchboardContext.Provider> | |
); | |
} | |
// ================ | |
interface SwitchboardButtonProps { | |
to: string; | |
} | |
function SwitchboardButton(props: SwitchboardButtonProps): JSX.Element { | |
const { switchTo } = useSwitchboardContext(); | |
const handleClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => { | |
if (props.onClick) { | |
props.onClick(event); | |
} | |
switchTo(props.to); | |
}; | |
return ( | |
<button onClick={handleClick} type="button" {...props}> | |
{props.children} | |
</button> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment