Created
March 31, 2021 21:49
-
-
Save itsMapleLeaf/2dc2c9b536016c9ca3e64f5404801c97 to your computer and use it in GitHub Desktop.
CSS transition state helper hook
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
import { useCallback, useEffect, useReducer } from "react" | |
type TransitionState = | |
// the initial frame of opening, where the elements are in the DOM, | |
// but the transition should start in the exit state | |
// without this initial frame, the transition would start at the entered state | |
| "opening-init" | |
// elements are in DOM, CSS transition should start | |
| "opening" | |
// in transition finished | |
| "opened" | |
// out transition started | |
// this doesn't need an "init" event, because we're already at the transition in state | |
| "closing" | |
// out transition finished | |
// elements should not be in the DOM | |
| "closed" | |
type TransitionEvent = | |
| "toggle" | |
| "open" | |
| "close" | |
| "transitionEnded" | |
| "openInitFinish" | |
function statusReducer( | |
status: TransitionState, | |
event: TransitionEvent, | |
): TransitionState { | |
// lazy person's state machine | |
if (status === "closed") { | |
if (event === "open") return "opening-init" | |
if (event === "toggle") return "opening-init" | |
} | |
if (status === "opening-init") { | |
if (event === "close") return "closing" | |
if (event === "toggle") return "closing" | |
if (event === "transitionEnded") return "opened" | |
if (event === "openInitFinish") return "opening" | |
} | |
if (status === "opening") { | |
if (event === "toggle") return "closing" | |
if (event === "close") return "closing" | |
if (event === "transitionEnded") return "opened" | |
} | |
if (status === "opened") { | |
if (event === "toggle") return "closing" | |
if (event === "close") return "closing" | |
} | |
if (status === "closing") { | |
if (event === "toggle") return "opening" | |
if (event === "open") return "opening" | |
if (event === "transitionEnded") return "closed" | |
} | |
return status | |
} | |
export default function useCssTransitionState(visible: boolean) { | |
const [state, dispatch] = useReducer(statusReducer, "closed") | |
useEffect(() => { | |
if (visible) { | |
dispatch("open") | |
requestAnimationFrame(() => dispatch("openInitFinish")) | |
} else { | |
dispatch("close") | |
} | |
}, [visible]) | |
return { | |
state, | |
transitionVisible: state === "opening" || state === "opened", | |
shouldRenderChildren: state !== "closed", | |
handleTransitionEnd: useCallback(() => dispatch("transitionEnded"), []), | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment