const TIMEOUT = 1000;
const reducer = (state, action) => {
switch (action.type) {
case 'start':
return {
isTransitioning: true,
shouldBeVisible: action.shouldBeVisible,
};
case 'finish':
return {
isTransitioning: false,
shouldBeVisible: state.shouldBeVisible,
};
default:
return state;
}
};
const _popoverStyles = {
opacity: 0,
transitionDuration: '300ms',
transitionProperty: 'opacity',
transitionTimingFunction: 'cubic-bezier(0, 0, 1, 1)',
};
const _popoverVisibleStyles = {
opacity: 1,
transitionDuration: '300ms',
transitionTimingFunction: 'cubic-bezier(0, 0, 1, 1)',
};
const useFadeEffect = (visible) => {
const ref = useRef(null);
const visibleRef = useRef(false);
const [state, setState] = useReducer(reducer, {
isTransitioning: false,
shouldBeVisible: false,
});
const { isTransitioning, shouldBeVisible } = state;
const hiddenRef = useRef(null);
const showRef = useRef(null);
useEffect(() => {
return () => {
if (hiddenRef.current != null) {
clearTimeout(hiddenRef.current);
}
if (showRef.current != null) {
window.cancelAnimationFrame(showRef.current);
}
};
}, []);
const handleFinish = useCallback(() => {
setState({
type: 'finish',
});
if (hiddenRef.current != null) {
clearTimeout(hiddenRef.current);
hiddenRef.current = null;
}
}, []);
const handleStart = useCallback(
(visible) => {
if (showRef.current != null) {
window.cancelAnimationFrame(showRef.current);
}
showRef.current = window.requestAnimationFrame(() => {
setState({
shouldBeVisible: visible,
type: 'start',
});
showRef.current = null;
if (hiddenRef.current != null) {
clearTimeout(hiddenRef.current);
hiddenRef.current = setTimeout(handleFinish, TIMEOUT);
}
});
},
[handleFinish],
);
useEffect(() => {
if (visibleRef.current !== visible && (!visible || ref.current != null)) {
handleStart(visible);
visibleRef.current = visible;
}
}, [visible, handleStart]);
const _ref = useCallback(
(_re) => {
const _elm = ref.current;
ref.current = _re;
if (_re !== null) {
if (_re.addEventListener !== null) {
_re.addEventListener('transitionend', handleFinish);
_re.addEventListener('webkitTransitionEnd', handleFinish);
}
if (visibleRef.current) {
handleStart(true);
}
} else if (_elm !== null) {
if (_elm.removeEventListener !== null) {
_elm.removeEventListener('transitionend', handleFinish);
_elm.removeEventListener('webkitTransitionEnd', handleFinish);
}
}
},
[handleFinish, handleStart],
);
const _isTransitioning = isTransitioning || shouldBeVisible || visible;
return [_isTransitioning, shouldBeVisible, _ref];
};
const Fade = ({ children, visible }) => {
const [_isTransitioning, shouldBeVisible, refModal] = useFadeEffect(visible);
return (
<>
{_isTransitioning && (
<div ref={refModal} style={shouldBeVisible ? _popoverVisibleStyles : _popoverStyles}>
{children}
</div>
)}
</>
);
};
const App = () => {
const [visible, setVisible] = useState(false);
return (
<div>
<button onClick={() => setVisible(!visible)}>Click</button>
<Fade visible={visible}>Hello World</Fade>
</div>
);
};
render(<App />);
jsx