import Masonry from 'react-masonry-css' import { useEffect, useState, useCallback, useRef } from '@wordpress/element' import { Spinner, Button } from '@wordpress/components' import { __, sprintf } from '@wordpress/i18n' import { useTemplatesStore } from '../state/Templates' import { Templates as TemplatesApi } from '../api/Templates' import { useInView } from 'react-intersection-observer' import { useIsMounted } from '../hooks/helpers' import { ImportTemplateBlock } from '../components/ImportTemplateBlock' export default function GridView() { const isMounted = useIsMounted() const templates = useTemplatesStore((state) => state.templates) const appendTemplates = useTemplatesStore((state) => state.appendTemplates) const [serverError, setServerError] = useState('') const [nothingFound, setNothingFound] = useState(false) const [loading, setLoading] = useState(false) const [loadMoreRef, inView] = useInView() const updateSearchParams = useTemplatesStore( (state) => state.updateSearchParams, ) const searchParamsRaw = useTemplatesStore((state) => state.searchParams) // Store the next page in case we have pagination const nextPage = useRef(useTemplatesStore.getState().nextPage) const searchParams = useRef(useTemplatesStore.getState().searchParams) const taxonomyType = searchParams.current.type === 'pattern' ? 'patternType' : 'layoutType' const currentTax = searchParams.current.taxonomies[taxonomyType] // Connect to the store on mount, disconnect on unmount, catch state-changes in a reference useEffect( () => useTemplatesStore.subscribe( (n) => (nextPage.current = n), (state) => state.nextPage, ), [], ) useEffect( () => useTemplatesStore.subscribe( (s) => (searchParams.current = s), (state) => state.searchParams, ), [], ) // Fetch the templates then add them to the current state // TODO: This works, but it's not really doing what it's intended to do // as it has a side effect in there, and isn't pure. // It could be extracted to a hook const fetchTemplates = useCallback(async () => { setServerError('') setNothingFound(false) const response = await TemplatesApi.get(searchParams.current, { offset: nextPage.current, }).catch((error) => { console.error(error) setServerError( error && error.message ? error.message : __( 'Unknown error occured. Check browser console or contact support.', 'extendify', ), ) }) if (!isMounted.current) { return } if (response?.error?.length) { setServerError(response?.error) } if (response?.records && searchParamsRaw === searchParams.current) { useTemplatesStore.setState({ nextPage: response.offset, }) appendTemplates(response.records) setNothingFound(response.records.length <= 0) setLoading(false) } }, [searchParamsRaw, appendTemplates, isMounted]) useEffect(() => { if (templates.length === 0) { setLoading(true) } }, [templates.length, searchParamsRaw]) // This is the main driver for loading templates // This loads the initial batch of templates. But if we don't yet have taxonomies. // There's also an option to skip loading on first mount useEffect(() => { if (!Object.keys(searchParams.current.taxonomies).length) { return } if (useTemplatesStore.getState().skipNextFetch) { // This is useful if the templates are fetched already and // the library moves to/from another state that re-renders // The point is to keep the logic close to the list. That may change someday useTemplatesStore.setState({ skipNextFetch: false, }) return } fetchTemplates() }, [fetchTemplates, searchParams]) // Fetches when the load more is in view useEffect(() => { nextPage.current && inView && fetchTemplates() }, [inView, fetchTemplates, templates]) if (serverError.length) { return (
{serverError}