From 09fa137a77b474a0a72e96815196dbca732fb348 Mon Sep 17 00:00:00 2001 From: Gianni Ferullo Date: Mon, 22 Apr 2024 08:05:57 -0400 Subject: [PATCH] react-components: support external state for grid allowing for easier data mutations --- .../react-components/src/components/grid.tsx | 16 ++- .../stories/grid-editable.stories.tsx | 117 ++++++++++++++++++ 2 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 packages/react-components/stories/grid-editable.stories.tsx diff --git a/packages/react-components/src/components/grid.tsx b/packages/react-components/src/components/grid.tsx index eba65d06..3e78a3f3 100644 --- a/packages/react-components/src/components/grid.tsx +++ b/packages/react-components/src/components/grid.tsx @@ -28,6 +28,7 @@ type Props = { noLink?: boolean noResultsMessage?: string | JSX.Element initialGifs?: IGif[] + externalGifs?: IGif[] useTransform?: boolean columnOffsets?: number[] backgroundColor?: string @@ -73,7 +74,7 @@ class Grid extends PureComponent { unmounted: boolean = false paginator = gifPaginator(this.props.fetchGifs, this.state.gifs) static getDerivedStateFromProps: GetDerivedStateFromProps = ( - { columns, gutter, width }: Props, + { columns, gutter, width, externalGifs }: Props, prevState: State ) => { const gutterOffset = gutter * (columns - 1) @@ -81,6 +82,9 @@ class Grid extends PureComponent { if (prevState.gifWidth !== gifWidth) { return { gifWidth } } + if (externalGifs && externalGifs !== prevState.gifs) { + return { gifs: externalGifs } + } return null } @@ -100,8 +104,13 @@ class Grid extends PureComponent { onFetch = debounce(Grid.fetchDebounce, async () => { if (this.unmounted) return - const { isFetching, isLoaderVisible, gifs } = this.state - const prefetchCount = gifs.length + const { isFetching, isLoaderVisible } = this.state + const { externalGifs, fetchGifs } = this.props + const prefetchCount = (externalGifs || this.state.gifs).length + if (externalGifs) { + // reinitialize the paginator every fetch with the new external gifs + this.paginator = gifPaginator(fetchGifs, externalGifs) + } if (!isFetching && isLoaderVisible) { this.setState({ isFetching: true, isError: false }) let gifs @@ -156,6 +165,7 @@ class Grid extends PureComponent { fetchPriority, } = this.props const { gifWidth, gifs, isError, isDoneFetching } = this.state + // const gifs = gifState || externalGifs const showLoader = !isDoneFetching const isFirstLoad = gifs.length === 0 // get the height of each grid item diff --git a/packages/react-components/stories/grid-editable.stories.tsx b/packages/react-components/stories/grid-editable.stories.tsx new file mode 100644 index 00000000..35a9f4eb --- /dev/null +++ b/packages/react-components/stories/grid-editable.stories.tsx @@ -0,0 +1,117 @@ +import styled from '@emotion/styled' +import { GiphyFetch } from '@giphy/js-fetch-api' +import { Meta, StoryObj } from '@storybook/react' +import fetchMock from 'fetch-mock' +import React, { useEffect, useState } from 'react' +import { throttle } from 'throttle-debounce' +import { GifOverlayProps, Grid as GridComponent } from '../src' +import inTestsRunner from './in-tests-runner' +import mockGifsResult from './mock-data/gifs.json' +import { IGif } from '@giphy/js-types' +import { giphyPurple } from '@giphy/colors' + +const apiKey = 'sXpGFDGZs0Dv1mmNFvYaGUvYwKX0PWIh' +const gf = new GiphyFetch(apiKey) + +const OverlayContainer = styled.div` + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + display: flex; + color: white; + justify-content: center; + align-items: center; +` +const ButtonContainer = styled.div` + display: flex; + flex-direction: column; + gap: 2px; + align-items: center; + h4 { + text-align: center; + } +` +const Button = styled.div` + cursor: pointer; + background: ${giphyPurple}; + padding: 6px; + border-radius: 4px; + max-width: fit-content; +` + +type GridProps = Partial> + +const Grid = ({ loader, ...other }: GridProps) => { + const [width, setWidth] = useState(innerWidth) + const [externalGifs, setExternalGifs] = useState() + const Overlay = ({ gif, isHovered }: GifOverlayProps) => ( + + {isHovered && ( + +

{gif.title}

+ + +
+ )} +
+ ) + const onResize = throttle(500, () => setWidth(innerWidth)) + useEffect(() => { + window.addEventListener('resize', onResize, false) + return () => window.removeEventListener('resize', onResize, false) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + const fetchGifs = async (offset: number) => { + if (inTestsRunner()) { + fetchMock.restore().getOnce(`begin:https://api.giphy.com/v1/gifs/search`, { body: mockGifsResult }) + } + const result = await gf.trending({ offset, limit: 10 }) + fetchMock.restore() + setExternalGifs(result.data) + return result + } + return ( + setExternalGifs(gifs)} + {...other} + /> + ) +} + +const meta: Meta = { + component: Grid, + title: 'React Components/Grid/Editable', +} + +export default meta + +type Story = StoryObj + +export const EditableGrid: Story = {}