Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

react-components: support external state for grid #442

Merged
merged 3 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/dull-cherries-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@giphy/react-components': patch
---

- Grid supports editableGifs
- make fetchpriority lowercase to avoid runtime warning
2 changes: 1 addition & 1 deletion packages/react-components/src/components/gif.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ const Gif = ({
suppressHydrationWarning
// @ts-ignore - fetchPriority is not recognized by React typescript
// eslint-disable-next-line react/no-unknown-property
fetchPriority={fetchPriority}
fetchpriority={fetchPriority}
className={[Gif.imgClassName, loadedClassname].join(' ')}
src={shouldShowMedia ? rendition.url : placeholder}
style={{ background }}
Expand Down
15 changes: 12 additions & 3 deletions packages/react-components/src/components/grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type Props = {
noLink?: boolean
noResultsMessage?: string | JSX.Element
initialGifs?: IGif[]
externalGifs?: IGif[]
useTransform?: boolean
columnOffsets?: number[]
backgroundColor?: string
Expand Down Expand Up @@ -73,14 +74,17 @@ class Grid extends PureComponent<Props, State> {
unmounted: boolean = false
paginator = gifPaginator(this.props.fetchGifs, this.state.gifs)
static getDerivedStateFromProps: GetDerivedStateFromProps<Props, State> = (
{ columns, gutter, width }: Props,
{ columns, gutter, width, externalGifs }: Props,
prevState: State
) => {
const gutterOffset = gutter * (columns - 1)
const gifWidth = Math.floor((width - gutterOffset) / columns)
if (prevState.gifWidth !== gifWidth) {
return { gifWidth }
}
if (externalGifs && externalGifs !== prevState.gifs) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if externalGifs exists, is this ever going to be false? i can't remember if === works with arrays of objects or not.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sketchybones yes this should be referentially false most of the time, the state in the grid is set to the external state. When you want to update the grid, pass in a new array. I do that in the Story when deleting a gif. If you edit the title, I don't update the array, although I probably should. But since the Gif component rerenders on hover, it grabs the latest title.

I guess if you want it to update instantly, you'd want to clone the array after changing the title:

 const editGif = externalGifs?.find((g) => g.id === gif.id)
  if (editGif) {
      editGif.title = result
      setExternalGifs([...externalGifs])
  }

return { gifs: externalGifs }
}
return null
}

Expand All @@ -100,8 +104,13 @@ class Grid extends PureComponent<Props, State> {

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
Expand Down
119 changes: 119 additions & 0 deletions packages/react-components/stories/grid-editable.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import styled from '@emotion/styled'
import { giphyPurple } from '@giphy/colors'
import { GiphyFetch } from '@giphy/js-fetch-api'
import { IGif } from '@giphy/js-types'
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'

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<React.ComponentProps<typeof GridComponent>>

const Grid = ({ loader, ...other }: GridProps) => {
const [width, setWidth] = useState(innerWidth - 24)
const [externalGifs, setExternalGifs] = useState<IGif[] | undefined>()
const Overlay = ({ gif, isHovered }: GifOverlayProps) => (
<OverlayContainer>
{isHovered && (
<ButtonContainer>
<h4>{gif.title}</h4>
<Button
onClick={() => {
setExternalGifs(externalGifs?.filter((g) => g.id !== gif.id))
}}
>
Delete Me
</Button>
<Button
onClick={() => {
const result = prompt('Edit Title', gif.title) || 'New Title'
if (externalGifs) {
const editGif = externalGifs?.find((g) => g.id === gif.id)
if (editGif) {
editGif.title = result
setExternalGifs([...externalGifs])
}
}
}}
>
Edit Title
</Button>
</ButtonContainer>
)}
</OverlayContainer>
)
const onResize = throttle(500, () => setWidth(innerWidth - 24))
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 (
<GridComponent
width={width}
noLink
columns={3}
loader={loader}
externalGifs={externalGifs}
fetchGifs={fetchGifs}
overlay={Overlay}
onGifsFetched={(gifs) => setExternalGifs(gifs)}
{...other}
/>
)
}

const meta: Meta<typeof Grid> = {
component: Grid,
title: 'React Components/Grid/Editable',
}

export default meta

type Story = StoryObj<typeof meta>

export const EditableGrid: Story = {}
Loading