window.ENTITIES={'/api/snippets/jsx/react%20-%20infinite%20scrolling%20(custom%20component)':[{"result":true,"message":null,"batch":{"type":"jsx","name":"react - infinite scrolling (custom component)","items":[{"id":"pa5oOp","type":"jsx","name":"React - infinite scrolling (custom component)","content":"// ONLINE-RUNNER:browser;\n\nimport React, { useRef, useState, useEffect, useMemo } from 'react';\n\n// Calculates element's absolute position.\n//\nconst calculateOffset = (element) => {\n if (element instanceof Window) {\n return 0;\n }\n let result = 0;\n while (element) {\n const value = element.offsetTop;\n if (value) {\n result += value;\n }\n element = element.offsetParent; // jumps to the next proper parent element\n }\n return result;\n};\n\n// Calculates space between elements.\n//\nconst calculateSpace = (aElement, bElement) => {\n const aOffset = calculateOffset(aElement);\n const bOffset = calculateOffset(bElement);\n return bOffset - aOffset;\n};\n\n// Calculates container's scroll position.\n//\nconst calculateScroll = (container) => {\n return container.scrollY || container.scrollTop || 0;\n};\n\n// Calculates container's inner height.\n//\nconst calculateHeight = (container) => {\n return container.innerHeight || container.clientHeight || 0;\n};\n\n// Checks barrier visibility inside container.\n//\nconst checkVisibility = (container, barrier, space) => {\n const barrierTop = calculateSpace(container, barrier);\n const containerScroll = calculateScroll(container);\n const containerHeight = calculateHeight(container);\n if (barrierTop < containerScroll + containerHeight + space) {\n return true;\n }\n return false;\n};\n\n// Creates logic that lets to track and control data loading using infinite scolling logic.\n//\nconst createManager = ({\n offset: _offset,\n space: _space,\n loader: _loader,\n container: _container,\n content: _content,\n barrier: _barrier,\n onLoading: _onLoading,\n onLoaded: _onLoaded,\n onError: _onError\n}) => {\n let _destroyed = false;\n let _enabled = false;\n let _loading = false;\n const signal = () => {\n if (_loading) {\n return;\n }\n if (_container.current == null || _barrier.current == null || checkVisibility(_container.current, _barrier.current, _space)) {\n _loading = true;\n if (_onLoading) {\n _onLoading();\n }\n const callback = (offset, data, error) => {\n if (_enabled) {\n _loading = false;\n try {\n if (error) {\n _onError(error);\n } else {\n _offset = offset;\n if (_onLoaded) {\n _onLoaded(offset, data);\n if (_enabled === false) {\n return;\n }\n }\n if (data && data.length > 0) {\n setTimeout(signal);\n }\n }\n } catch (e) {\n console.error(e);\n }\n }\n };\n try {\n _loader(_offset, callback);\n } catch (e) {\n _loading = false;\n if (_onError) {\n _onError('Loading error.');\n }\n }\n }\n };\n return {\n signal: () => {\n if (_destroyed) {\n throw new Error('Object has been destroyed.');\n }\n if (_enabled) {\n signal(); \n }\n },\n enable: () => {\n if (_destroyed) {\n throw new Error('Object has been destroyed.');\n }\n if (_enabled) {\n return;\n }\n _enabled = true;\n _container.current.addEventListener('scroll', signal, false);\n signal();\n },\n disable: () => {\n if (_destroyed) {\n throw new Error('Object has been destroyed.');\n }\n if (_enabled) {\n _enabled = false;\n _container.current.removeEventListener('scroll', signal, false);\n }\n },\n destroy: () => {\n if (_destroyed) {\n return;\n }\n _destroyed = true;\n _enabled = false;\n _container.current.removeEventListener('scroll', signal, false);\n }\n };\n};\n\n// Creates function proxy (Source: https://dirask.com/snippets/jmJNN1).\n//\nconst createProxy = () => {\n const state = {\n wrapper: (...args) => {\n if (state.action) {\n return state.action(...args);\n }\n return undefined;\n },\n action: null\n };\n return state;\n};\n\n// Privides proxy hook (Source: https://dirask.com/snippets/jmJNN1).\n//\nconst useProxy = (action) => {\n const proxy = useMemo(createProxy, []);\n proxy.action = action;\n return proxy.wrapper;\n};\n\n// Component that implements infinite scolling logic.\n//\nconst InfinityScrolling = ({\n containerRef,\n pageSize = 20,\n spaceSize = 100,\n dataLoader,\n itemRenderer\n}) => {\n const contentRef = useRef(null);\n const barrierRef = useRef(null);\n const [offset, setOffset] = useState(0);\n const [pages, setPages] = useState(null);\n const [state, setState] = useState(null);\n const loaderProxy = useProxy(dataLoader);\n const manager = useMemo(\n () => {\n return createManager({\n offset: offset,\n space: spaceSize,\n loader: loaderProxy,\n container: containerRef,\n content: contentRef,\n barrier: barrierRef,\n onLoading: () => {\n setState('Loading...');\n },\n onLoaded: (offset, items) => {\n if (items.length > 0) {\n setOffset(offset);\n setPages(page => page ? [...page, items] : [items]);\n }\n if (items.length < pageSize) {\n setState(null);\n manager.disable();\n } else {\n setState('Click me to continue loading...');\n }\n },\n onError: (error) => {\n setState('Loading error! (click me to continue)');\n }\n });\n },\n [containerRef]\n );\n useEffect(\n () => {\n manager.enable();\n return () => {\n manager.disable();\n };\n },\n [containerRef]\n );\n const handleClick = () => manager.signal();\n return (\n