Skip to content

Commit

Permalink
[GEN-1772]: fix "Transition" re-renders (#1819)
Browse files Browse the repository at this point in the history
  • Loading branch information
BenElferink authored Nov 21, 2024
1 parent 6ef2a04 commit 28c3eee
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { slide } from '@/styles';
import theme from '@/styles/theme';
import { useAppStore } from '@/store';
import styled from 'styled-components';
import { useSourceCRUD } from '@/hooks';
import { DeleteWarning } from '@/components';
import { Badge, Button, Divider, Text, Transition } from '@/reuseable-components';
import { useSourceCRUD, useTransition } from '@/hooks';
import { Badge, Button, Divider, Text } from '@/reuseable-components';

const Container = styled.div`
position: fixed;
Expand All @@ -24,6 +24,12 @@ const Container = styled.div`
`;

const MultiSourceControl = () => {
const Transition = useTransition({
container: Container,
animateIn: slide.in['center'],
animateOut: slide.out['center'],
});

const { sources, deleteSources } = useSourceCRUD();
const { configuredSources, setConfiguredSources } = useAppStore((state) => state);
const [isWarnModalOpen, setIsWarnModalOpen] = useState(false);
Expand All @@ -50,7 +56,7 @@ const MultiSourceControl = () => {

return (
<>
<Transition container={Container} enter={!!totalSelected} animateIn={slide.in['center']} animateOut={slide.out['center']}>
<Transition enter={!!totalSelected}>
<Text>Selected sources</Text>
<Badge label={totalSelected} filled />

Expand Down
1 change: 1 addition & 0 deletions frontend/webapp/hooks/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './useContainerSize';
export * from './useOnClickOutside';
export * from './useKeyDown';
export * from './useTimeAgo';
export * from './useTransition';
42 changes: 42 additions & 0 deletions frontend/webapp/hooks/common/useTransition.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React, { PropsWithChildren, useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';
import type { IStyledComponentBase, Keyframes, Substitute } from 'styled-components/dist/types';

interface HookProps {
container: IStyledComponentBase<'web', Substitute<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>, {}>> & string;
animateIn: Keyframes;
animateOut?: Keyframes;
duration?: number; // in milliseconds
}

type TransitionProps = PropsWithChildren<{
enter: boolean;
[key: string]: any;
}>;

export const useTransition = ({ container, animateIn, animateOut, duration = 300 }: HookProps) => {
const Animated = styled(container)<{ $isEntering: boolean; $isLeaving: boolean }>`
animation-name: ${({ $isEntering, $isLeaving }) => ($isEntering ? animateIn : $isLeaving ? animateOut : 'none')};
animation-duration: ${duration}ms;
animation-fill-mode: forwards;
`;

const Transition = useCallback(({ children, enter, ...props }: TransitionProps) => {
const [mounted, setMounted] = useState(false);

useEffect(() => {
const t = setTimeout(() => setMounted(enter), duration + 50); // +50ms to ensure the animation is finished
return () => clearTimeout(t);
}, [enter, duration]);

return (
<Animated $isEntering={enter} $isLeaving={!enter && mounted} {...props}>
{children}
</Animated>
);

// do not add dependencies here, it will cause re-renders which we want to avoid
}, []);

return Transition;
};
34 changes: 16 additions & 18 deletions frontend/webapp/reuseable-components/drawer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { useKeyDown } from '@/hooks';
import styled from 'styled-components';
import { slide, Overlay } from '@/styles';
import { useKeyDown, useTransition } from '@/hooks';

interface DrawerProps {
interface Props {
isOpen: boolean;
onClose: () => void;
closeOnEscape?: boolean;
Expand All @@ -13,11 +13,9 @@ interface DrawerProps {
children: React.ReactNode;
}

// Styled-component for drawer container
const DrawerContainer = styled.div<{
$isOpen: DrawerProps['isOpen'];
$position: DrawerProps['position'];
$width: DrawerProps['width'];
const Container = styled.div<{
$position: Props['position'];
$width: Props['width'];
}>`
position: fixed;
top: 0;
Expand All @@ -28,26 +26,26 @@ const DrawerContainer = styled.div<{
background: ${({ theme }) => theme.colors.translucent_bg};
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
overflow-y: auto;
animation: ${({ $isOpen, $position = 'right' }) => ($isOpen ? slide.in[$position] : slide.out[$position])} 0.3s ease;
`;

export const Drawer: React.FC<DrawerProps> = ({ isOpen, onClose, position = 'right', width = '300px', children, closeOnEscape = true }) => {
useKeyDown(
{
key: 'Escape',
active: isOpen && closeOnEscape,
},
() => onClose(),
);
export const Drawer: React.FC<Props> = ({ isOpen, onClose, position = 'right', width = '300px', children, closeOnEscape = true }) => {
useKeyDown({ key: 'Escape', active: isOpen && closeOnEscape }, () => onClose());

const Transition = useTransition({
container: Container,
animateIn: slide.in[position],
animateOut: slide.out[position],
});

if (!isOpen) return null;

return ReactDOM.createPortal(
<>
<Overlay hidden={!isOpen} onClick={onClose} />
<DrawerContainer $isOpen={isOpen} $position={position} $width={width}>

<Transition enter={isOpen} $position={position} $width={width}>
{children}
</DrawerContainer>
</Transition>
</>,
document.body,
);
Expand Down
1 change: 0 additions & 1 deletion frontend/webapp/reuseable-components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,3 @@ export * from './drawer';
export * from './input-table';
export * from './status';
export * from './field-label';
export * from './transition';
29 changes: 14 additions & 15 deletions frontend/webapp/reuseable-components/modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import React from 'react';
import Image from 'next/image';
import { Text } from '../text';
import ReactDOM from 'react-dom';
import { useKeyDown } from '@/hooks';
import styled from 'styled-components';
import { useKeyDown, useTransition } from '@/hooks';
import { slide, Overlay, CenterThis } from '@/styles';

interface ModalProps {
interface Props {
isOpen: boolean;
noOverlay?: boolean;
header?: {
Expand All @@ -17,7 +17,7 @@ interface ModalProps {
children: React.ReactNode;
}

const ModalWrapper = styled.div<{ $isOpen: ModalProps['isOpen'] }>`
const Container = styled.div`
position: fixed;
top: 50%;
left: 50%;
Expand All @@ -30,7 +30,6 @@ const ModalWrapper = styled.div<{ $isOpen: ModalProps['isOpen'] }>`
border-radius: 40px;
box-shadow: 0px 1px 1px 0px rgba(17, 17, 17, 0.8), 0px 2px 2px 0px rgba(17, 17, 17, 0.8), 0px 5px 5px 0px rgba(17, 17, 17, 0.8), 0px 10px 10px 0px rgba(17, 17, 17, 0.8),
0px 0px 8px 0px rgba(17, 17, 17, 0.8);
animation: ${({ $isOpen }) => ($isOpen ? slide.in['center'] : slide.out['center'])} 0.3s ease;
`;

const ModalHeader = styled.div`
Expand Down Expand Up @@ -83,22 +82,22 @@ const CancelText = styled(Text)`
cursor: pointer;
`;

const Modal: React.FC<ModalProps> = ({ isOpen, noOverlay, header, actionComponent, onClose, children }) => {
useKeyDown(
{
key: 'Escape',
active: isOpen,
},
() => onClose(),
);
const Modal: React.FC<Props> = ({ isOpen, noOverlay, header, actionComponent, onClose, children }) => {
useKeyDown({ key: 'Escape', active: isOpen }, () => onClose());

const Transition = useTransition({
container: Container,
animateIn: slide.in['center'],
animateOut: slide.out['center'],
});

if (!isOpen) return null;

return ReactDOM.createPortal(
<>
<Overlay onClick={onClose} style={{ opacity: noOverlay ? 0 : 1 }} />
<Overlay hidden={!isOpen} onClick={onClose} style={{ opacity: noOverlay ? 0 : 1 }} />

<ModalWrapper $isOpen={isOpen}>
<Transition enter={isOpen}>
{header && (
<ModalHeader>
<ModalCloseButton onClick={onClose}>
Expand All @@ -113,7 +112,7 @@ const Modal: React.FC<ModalProps> = ({ isOpen, noOverlay, header, actionComponen
)}

<ModalContent>{children}</ModalContent>
</ModalWrapper>
</Transition>
</>,
document.body,
);
Expand Down
36 changes: 0 additions & 36 deletions frontend/webapp/reuseable-components/transition/index.tsx

This file was deleted.

0 comments on commit 28c3eee

Please sign in to comment.