Created
July 28, 2021 16:07
-
-
Save esmevane/628d87594abac733ad3cdaa2cd573534 to your computer and use it in GitHub Desktop.
[ React / Typescript ]: Example of an architectural journey with a React Toggle component
This file contains hidden or 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
/* eslint-disable @typescript-eslint/require-await */ | |
import { render } from '@testing-library/react'; | |
import userEvent from '@testing-library/user-event'; | |
import { createContext, useContext, useReducer } from 'react'; | |
type Events = { type: 'toggle' } | { type: 'reset' }; | |
interface EventMap { | |
toggle: () => void; | |
reset: () => void; | |
} | |
interface State { | |
active: boolean; | |
dangerLevel: number; | |
} | |
const initial: State = { active: false, dangerLevel: 1 }; | |
const update = (state: State, event: Events): State => { | |
switch (event.type) { | |
case 'toggle': | |
return { active: !state.active, dangerLevel: state.dangerLevel + 1 }; | |
case 'reset': | |
return initial; | |
} | |
}; | |
const ToggleStateContext = createContext<State>(initial); | |
const ToggleEventContext = createContext<EventMap>({ | |
reset: () => undefined, | |
toggle: () => undefined, | |
}); | |
function useToggle() { | |
const [state, dispatch] = useReducer(update, initial); | |
const events = { | |
toggle: () => dispatch({ type: 'toggle' }), | |
reset: () => dispatch({ type: 'reset' }), | |
}; | |
return [state, events] as const; | |
} | |
function useToggleEvents() { | |
return useContext(ToggleEventContext); | |
} | |
function ToggleReset() { | |
const events = useToggleEvents(); | |
return <button onClick={events.reset}>Reset</button>; | |
} | |
function Toggle(props: React.PropsWithChildren<unknown>) { | |
const [state, events] = useToggle(); | |
return ( | |
<ToggleStateContext.Provider value={state}> | |
<ToggleEventContext.Provider value={events}> | |
{state.dangerLevel > 5 ? ( | |
<> | |
<span>Explosions!</span> | |
{props.children} | |
</> | |
) : ( | |
<> | |
<button onClick={events.toggle}>Toggle</button> | |
{state.active ? 'On' : 'Off'} | |
</> | |
)} | |
</ToggleEventContext.Provider> | |
</ToggleStateContext.Provider> | |
); | |
} | |
test('toggles start off disabled', async () => { | |
const component = render(<Toggle />); | |
await component.findByText('Off'); | |
}); | |
test('toggles on when clicked', async () => { | |
const component = render(<Toggle />); | |
userEvent.click(await component.findByText('Toggle')); | |
await component.findByText('On'); | |
}); | |
test('toggles off again', async () => { | |
const component = render(<Toggle />); | |
userEvent.click(await component.findByText('Toggle')); | |
userEvent.click(await component.findByText('Toggle')); | |
await component.findByText('Off'); | |
}); | |
test('toggles can break', async () => { | |
const component = render(<Toggle />); | |
userEvent.click(await component.findByText('Toggle')); | |
userEvent.click(await component.findByText('Toggle')); | |
userEvent.click(await component.findByText('Toggle')); | |
userEvent.click(await component.findByText('Toggle')); | |
userEvent.click(await component.findByText('Toggle')); | |
await component.findByText('Explosions!'); | |
}); | |
test('toggles can reset after breaking', async () => { | |
const component = render( | |
<Toggle> | |
<ToggleReset /> | |
</Toggle> | |
); | |
userEvent.click(await component.findByText('Toggle')); | |
userEvent.click(await component.findByText('Toggle')); | |
userEvent.click(await component.findByText('Toggle')); | |
userEvent.click(await component.findByText('Toggle')); | |
userEvent.click(await component.findByText('Toggle')); | |
await component.findByText('Explosions!'); | |
userEvent.click(await component.findByText('Reset')); | |
await component.findByText('Off'); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment