Skip to content

Commit

Permalink
React | Containers: Component lifecycle optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
rokotyan committed Feb 6, 2023
1 parent 55c8dbc commit f6c77c6
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 20 deletions.
31 changes: 21 additions & 10 deletions packages/react/src/containers/single-container/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ export type VisSingleContainerProps<Data> = SingleContainerConfigInterface<Data>
// eslint-disable-next-line @typescript-eslint/naming-convention
function VisSingleContainerFC<Data> (props: PropsWithChildren<VisSingleContainerProps<Data>>): JSX.Element {
const container = useRef<HTMLDivElement>(null)
const [chart, setChart] = useState<SingleContainer<Data>>()
const [data, setData] = useState<Data | undefined>(undefined)
const prevPropsRef = useRef<PropsWithChildren<VisSingleContainerProps<Data>>>({})
const chartRef = useRef<SingleContainer<Data> | undefined>(undefined)
const dataRef = useRef<Data | undefined>(undefined)
const animationFrameRef = useRef<number | null>(null)

const getConfig = (): SingleContainerConfigInterface<Data> => ({
Expand All @@ -29,11 +30,16 @@ function VisSingleContainerFC<Data> (props: PropsWithChildren<VisSingleContainer

// On Mount
useEffect(() => {
setData(props.data)
const c = new SingleContainer<Data>(container.current as HTMLDivElement, getConfig(), props.data)
setChart(c)
chartRef.current = c
dataRef.current = props.data

return () => {
if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current)
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current)
animationFrameRef.current = null
prevPropsRef.current = {}
}
c.destroy()
}
}, [])
Expand All @@ -43,9 +49,9 @@ function VisSingleContainerFC<Data> (props: PropsWithChildren<VisSingleContainer
const preventRender = true

// Set new Data without re-render
if (props.data && (props.data !== data)) {
chart?.setData(props.data, preventRender)
setData(props.data)
if (props.data && (props.data !== dataRef.current)) {
chartRef.current?.setData(props.data, preventRender)
dataRef.current = props.data
}

// Update and render
Expand All @@ -55,8 +61,13 @@ function VisSingleContainerFC<Data> (props: PropsWithChildren<VisSingleContainer
// that will be destroyed soon) are stored in the `__component__` property of their elements at that moment.
// So we delay the container update with `requestAnimationFrame` to wait till the new instances of children
// components are available at `__component__`.
if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current)
animationFrameRef.current = requestAnimationFrame(() => chart?.updateContainer(getConfig()))
if (!arePropsEqual(prevPropsRef.current, props)) { // Checking whether the props have changed do avoid multiple renders
prevPropsRef.current = props
if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current)
animationFrameRef.current = requestAnimationFrame(() => {
chartRef.current?.updateContainer(getConfig())
})
}
})

return (
Expand Down
30 changes: 20 additions & 10 deletions packages/react/src/containers/xy-container/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ export type VisXYContainerProps<Datum> = XYContainerConfigInterface<Datum> & {
// eslint-disable-next-line @typescript-eslint/naming-convention
export function VisXYContainerFC<Datum> (props: PropsWithChildren<VisXYContainerProps<Datum>>): JSX.Element {
const container = useRef<HTMLDivElement>(null)
const [chart, setChart] = useState<XYContainer<Datum>>()
const [data, setData] = useState<Datum[] | undefined>(undefined)
const prevPropsRef = useRef<PropsWithChildren<VisXYContainerProps<Datum>>>({})
const chartRef = useRef<XYContainer<Datum> | undefined>(undefined)
const dataRef = useRef<Datum[] | undefined>(undefined)
const animationFrameRef = useRef<number | null>(null)

const getConfig = (): XYContainerConfigInterface<Datum> => ({
Expand All @@ -40,12 +41,16 @@ export function VisXYContainerFC<Datum> (props: PropsWithChildren<VisXYContainer

// On Mount
useEffect(() => {
setData(props.data)
const c = new XYContainer<Datum>(container.current as HTMLDivElement, getConfig(), props.data)
setChart(c)
chartRef.current = c
dataRef.current = props.data

return () => {
if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current)
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current)
animationFrameRef.current = null
prevPropsRef.current = {}
}
c.destroy()
}
}, [])
Expand All @@ -55,9 +60,9 @@ export function VisXYContainerFC<Datum> (props: PropsWithChildren<VisXYContainer
const preventRender = true

// Set new Data without re-render
if (props.data && (props.data !== data)) {
chart?.setData(props.data, preventRender)
setData(props.data)
if (props.data && (props.data !== dataRef.current)) {
chartRef.current?.setData(props.data, preventRender)
dataRef.current = props.data
}

// Update and render
Expand All @@ -67,8 +72,13 @@ export function VisXYContainerFC<Datum> (props: PropsWithChildren<VisXYContainer
// that will be destroyed soon) are stored in the `__component__` property of their elements at that moment.
// So we delay the container update with `requestAnimationFrame` to wait till the new instances of children
// components are available at `__component__`.
if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current)
animationFrameRef.current = requestAnimationFrame(() => chart?.updateContainer(getConfig()))
if (!arePropsEqual(prevPropsRef.current, props)) { // Checking whether the props have changed do avoid multiple renders
prevPropsRef.current = props
if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current)
animationFrameRef.current = requestAnimationFrame(() => {
chartRef.current?.updateContainer(getConfig())
})
}
})

return (
Expand Down

0 comments on commit f6c77c6

Please sign in to comment.