From 5acac741e4f37fb4272ef1c74b5a65c260555eba Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 11 Aug 2020 13:26:51 -0600 Subject: [PATCH] fix: improve performance --- .../tabs/Comments/Stream/StreamContainer.tsx | 11 +- .../Stream/ViewersWatchingContainer.tsx | 174 ++++++++++-------- 2 files changed, 108 insertions(+), 77 deletions(-) diff --git a/src/core/client/stream/tabs/Comments/Stream/StreamContainer.tsx b/src/core/client/stream/tabs/Comments/Stream/StreamContainer.tsx index 9de54f730..6a3d8ccd0 100644 --- a/src/core/client/stream/tabs/Comments/Stream/StreamContainer.tsx +++ b/src/core/client/stream/tabs/Comments/Stream/StreamContainer.tsx @@ -10,6 +10,7 @@ import { graphql } from "react-relay"; import { useCoralContext } from "coral-framework/lib/bootstrap"; import { useViewerEvent } from "coral-framework/lib/events"; +import { IntersectionProvider } from "coral-framework/lib/intersection"; import { useLocal, withFragmentContainer } from "coral-framework/lib/relay"; import { GQLSTORY_MODE, GQLUSER_STATUS } from "coral-framework/schema"; import CLASSES from "coral-stream/classes"; @@ -228,10 +229,12 @@ export const StreamContainer: FunctionComponent = (props) => { settings={props.settings} /> )} - + + + ; story: ViewersWatchingContainer_story; settings: ViewersWatchingContainer_settings; } const TIMEOUT = 20000; -const JITTER = 10000; -function getTimeout() { - return TIMEOUT + Math.floor(Math.random() * JITTER); -} +const TIMEOUT_JITTER = TIMEOUT / 2; +const MAX_TIMEOUT = TIMEOUT + TIMEOUT_JITTER; const ViewersWatchingContainer: FunctionComponent = ({ story, settings, + inView = false, + intersectionRef, }) => { + const [lastRefreshed, setLastRefreshed] = useState(Date.now()); const [refreshed, setRefreshed] = useState(false); const live = useLive({ story, settings }); const visible = useVisibilityState(); const refreshStoryViewerCount = useFetch(RefreshStoryViewerCount); + // refresh will refresh the viewer count by refetching the data via the Graph. + const refresh = useCallback(async () => { + try { + // Refresh the viewer count! + await refreshStoryViewerCount({ storyID: story.id }); + + // Mark that we've refreshed (so we remove the extra +1). + setRefreshed(true); + + // Mark the current date so it'll schedule the next timeout to run in the + // following useEffect. + setLastRefreshed(Date.now()); + } catch (err) { + if (process.env.NODE_ENV !== "production") { + // eslint-disable-next-line no-console + console.error("couldn not refresh the story viewer count:", err); + } + } + }, [refreshStoryViewerCount, story.id]); + // available will be true when the viewer count is available. const available = story.viewerCount !== null; useEffect(() => { - // If we aren't live, we can't refresh the count cause there can't be any! - if (!live || !visible || !available) { + // If we aren't live, or there isn't a live count available (like if the + // feature flag isn't enabled), then we don't have to do anything! If the + // element isn't visible or the page isn't in the foreground, also halt + // updates. + if (!live || !available || !visible || !inView) { return; } - // Setup a timeout to be re-used. - let timeout: number | null = null; - let disposed = false; + // Get the time between now and the last time we updated. This addresses the + // issue where the timer was cleared and not reset because it was out of + // view so it'll fire right now. + const lastRefreshedDiff = Date.now() - lastRefreshed; + if (lastRefreshedDiff >= MAX_TIMEOUT) { + // The difference was greater than the max timeout. Fire the refresh right + // now. + void refresh(); + return; + } - // Create a function that can be used to refresh the story viewer count. - const refresh = async () => { - try { - // Refresh the viewer count! - await refreshStoryViewerCount({ storyID: story.id }); + const timeout = window.setTimeout( + refresh, + // Start with the max timeout... + MAX_TIMEOUT - + // Then subtract the difference from the last refresh date... + lastRefreshedDiff + + // And add a random jitter to help spread out the calls. + Math.floor(Math.random() * TIMEOUT_JITTER) + ); - // If we're disposed, then stop now! - if (disposed) { - return; - } - - // Mark that we've refreshed (so we remove the extra +1). - setRefreshed(true); - - // Add this back with a timeout if we aren't disposed. - timeout = window.setTimeout(refresh, getTimeout()); - } catch (err) { - if (process.env.NODE_ENV !== "production") { - // eslint-disable-next-line no-console - console.error("couldn not refresh the story viewer count:", err); - } - } - }; - - // Configure a timeout to fire later to refresh. - timeout = window.setTimeout(refresh, getTimeout()); - - // Clear the timeout when we dispose. return () => { - // Clear the timeout if it's still running. - if (timeout) { - window.clearTimeout(timeout); - } - - // Mark this as disposed so we don't update state after a refresh. - disposed = true; + window.clearTimeout(timeout); }; - }, [live, story.id, refreshStoryViewerCount, visible, available]); + }, [ + live, + story.id, + refreshStoryViewerCount, + visible, + available, + inView, + lastRefreshed, + refresh, + ]); // If we aren't live or the viewer count isn't available, then return nothing! if (!live || story.viewerCount === null) { @@ -95,39 +119,43 @@ const ViewersWatchingContainer: FunctionComponent = ({ const viewerCount = refreshed ? story.viewerCount : story.viewerCount + 1; return ( - play_circle_filled} - title={ - - {viewerCount} people is online - - } - titleWeight="semiBold" - /> +
+ play_circle_filled} + title={ + + {viewerCount} people is online + + } + titleWeight="semiBold" + /> +
); }; -const enhanced = withFragmentContainer({ - story: graphql` - fragment ViewersWatchingContainer_story on Story { - id - viewerCount - isClosed - settings { - live { +const enhanced = withInView( + withFragmentContainer({ + story: graphql` + fragment ViewersWatchingContainer_story on Story { + id + viewerCount + isClosed + settings { + live { + enabled + } + } + } + `, + settings: graphql` + fragment ViewersWatchingContainer_settings on Settings { + disableCommenting { enabled } } - } - `, - settings: graphql` - fragment ViewersWatchingContainer_settings on Settings { - disableCommenting { - enabled - } - } - `, -})(ViewersWatchingContainer); + `, + })(ViewersWatchingContainer) +); export default enhanced;