Grid.js (5849B)
1 import { 2 useEffect, useState, useCallback, useRef, 3 } from '@wordpress/element' 4 import { useTemplatesStore } from '../../state/Templates' 5 import { Templates as TemplatesApi } from '../../api/Templates' 6 import { useInView } from 'react-intersection-observer' 7 import { Spinner, Button } from '@wordpress/components' 8 import { __, sprintf } from '@wordpress/i18n' 9 import { useIsMounted } from '../../hooks/helpers' 10 import TemplateButton from '../../components/TemplateButton' 11 12 export default function TemplatesList() { 13 const isMounted = useIsMounted() 14 const templates = useTemplatesStore(state => state.templates) 15 const setActiveTemplate = useTemplatesStore(state => state.setActive) 16 const appendTemplates = useTemplatesStore(state => state.appendTemplates) 17 const [serverError, setServerError] = useState('') 18 const [nothingFound, setNothingFound] = useState(false) 19 // const [imagesLoaded, setImagesLoaded] = useState([]) 20 const [loadMoreRef, inView] = useInView() 21 22 const updateSearchParams = useTemplatesStore(state => state.updateSearchParams) 23 const searchParamsRaw = useTemplatesStore(state => state.searchParams) 24 25 // Store the next page in case we have pagination 26 const nextPage = useRef(useTemplatesStore.getState().nextPage) 27 const searchParams = useRef(useTemplatesStore.getState().searchParams) 28 // Connect to the store on mount, disconnect on unmount, catch state-changes in a reference 29 useEffect(() => useTemplatesStore.subscribe(n => (nextPage.current = n), 30 state => state.nextPage), []) 31 useEffect(() => useTemplatesStore.subscribe(s => (searchParams.current = s), 32 state => state.searchParams), []) 33 34 // Fetch the templates then add them to the current state 35 // TODO: This works, but it's not really doing what it's intended to do 36 // as it has a side effect in there, and isn't pure. 37 // It could be extracted to a hook 38 const fetchTemplates = useCallback(async () => { 39 setServerError('') 40 setNothingFound(false) 41 const response = await TemplatesApi.get(searchParams.current, { offset: nextPage.current }) 42 .catch((error) => { 43 console.error(error) 44 setServerError(error && error.message 45 ? error.message 46 : __('Unknown error occured. Check browser console or contact support.', 'extendify-sdk')) 47 }) 48 if (!isMounted.current) { 49 return 50 } 51 if (response?.error?.length) { 52 setServerError(response?.error) 53 } 54 if (response?.records && searchParamsRaw === searchParams.current) { 55 useTemplatesStore.setState({ 56 nextPage: response.offset, 57 }) 58 appendTemplates(response.records) 59 setNothingFound(response.records.length <= 0) 60 } 61 }, [searchParamsRaw, appendTemplates, isMounted]) 62 63 // This is the main driver for loading templates 64 // This loads the initial batch of templates. But if we don't yet have taxonomies. 65 // There's also an option to skip loading on first mount 66 useEffect(() => { 67 if (!Object.keys(searchParams.current.taxonomies).length) { 68 return 69 } 70 71 if (useTemplatesStore.getState().skipNextFetch) { 72 // This is useful if the templates are fetched already and 73 // the library moves to/from another state that re-renders 74 // The point is to keep the logic close to the list. That may change someday 75 useTemplatesStore.setState({ 76 skipNextFetch: false, 77 }) 78 return 79 } 80 // setImagesLoaded([]) 81 fetchTemplates() 82 }, [fetchTemplates, searchParams]) 83 84 // Fetches when the load more is in view 85 useEffect(() => { 86 inView && fetchTemplates() 87 }, [inView, fetchTemplates]) 88 89 if (serverError.length) { 90 return <div className="text-left"> 91 <h2 className="text-left">{__('Server error', 'extendify-sdk')}</h2> 92 <code className="block max-w-xl p-4 mb-4" style={{ 93 minHeight: '10rem', 94 }}>{serverError}</code> 95 <Button isTertiary onClick={() => { 96 // setImagesLoaded([]) 97 updateSearchParams({ 98 taxonomies: {}, 99 search: '', 100 }) 101 fetchTemplates() 102 }}>{ __('Press here to reload experience')}</Button> 103 </div> 104 } 105 106 if (nothingFound) { 107 if (searchParamsRaw?.search.length) { 108 return <h2 className="text-left"> 109 {sprintf(__('No results for %s.', 'extendify-sdk'), searchParamsRaw?.search)} 110 </h2> 111 } 112 return <h2 className="text-left">{__('No results found.', 'extendify-sdk')}</h2> 113 } 114 115 if (!templates.length) { 116 return <div className="flex items-center justify-center w-full sm:mt-64"> 117 <Spinner/> 118 </div> 119 } 120 121 return <> 122 <ul className="flex-grow gap-6 grid xl:grid-cols-2 2xl:grid-cols-3 pb-32 m-0"> 123 {templates.map((template) => { 124 return <li key={template.id}> 125 <TemplateButton 126 template={template} 127 setActiveTemplate={() => setActiveTemplate(template)} 128 imageLoaded={() => {}} 129 /> 130 </li> 131 })} 132 </ul> 133 {useTemplatesStore.getState().nextPage && <> 134 <div 135 className="-translate-y-full flex flex-col h-80 items-end justify-end my-2 relative transform z-0 text" 136 ref={loadMoreRef} 137 style={{ zIndex: -1 }}> 138 </div> 139 <div className="my-4"> 140 <Spinner/> 141 </div> 142 </>} 143 </> 144 }