Refactor TemplateEditor and TemplatePreview components for improved performance and error handling
Some checks failed
farmcontrol/farmcontrol-ui/pipeline/head There was a failure building this commit

- Updated TemplateEditor to use useCallback for handlePreviewMessage, optimizing performance.
- Enhanced TemplatePreview by adding default no-op functions for onTestObjectOpen and onPreviewMessage props.
- Improved error handling in fetchTemplatePDF and fetchTemplatePreview methods to ensure callbacks are only invoked if they are functions.
- Refactored preview loading logic to prevent unnecessary calls when template ID is not available.
This commit is contained in:
Tom Butcher 2026-07-28 01:50:16 +01:00
parent 59d16f80aa
commit f743f9b892
3 changed files with 52 additions and 15 deletions

View File

@ -1,4 +1,4 @@
import { useState } from 'react' import { useState, useCallback } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { import {
Flex, Flex,
@ -31,10 +31,10 @@ const TemplateEditor = ({
const [previewError, setPreviewError] = useState(false) const [previewError, setPreviewError] = useState(false)
//const isMobile = useMediaQuery({ maxWidth: 768 }) //const isMobile = useMediaQuery({ maxWidth: 768 })
const handlePreviewMessage = (message, isError) => { const handlePreviewMessage = useCallback((message, isError) => {
setPreviewMessage(message) setPreviewMessage(message)
setPreviewError(isError) setPreviewError(isError)
} }, [])
return ( return (
<> <>

View File

@ -7,17 +7,20 @@ import InfoCircleIcon from '../../Icons/InfoCircleIcon.jsx'
import ObjectProperty from '../common/ObjectProperty.jsx' import ObjectProperty from '../common/ObjectProperty.jsx'
import { ApiServerContext } from '../context/ApiServerContext.jsx' import { ApiServerContext } from '../context/ApiServerContext.jsx'
const noop = () => {}
const TemplatePreview = ({ const TemplatePreview = ({
objectData, objectData,
documentTemplate, documentTemplate,
loading, loading,
isEditing, isEditing,
onTestObjectOpen, onTestObjectOpen = noop,
onPreviewMessage, onPreviewMessage = noop,
showTestObject = false, showTestObject = false,
showPreviewSwitch = true showPreviewSwitch = true
}) => { }) => {
const iframeRef = useRef(null) const iframeRef = useRef(null)
const previewRequestIdRef = useRef(0)
const { fetchTemplatePreview, fetchTemplatePDF } = const { fetchTemplatePreview, fetchTemplatePDF } =
useContext(ApiServerContext) useContext(ApiServerContext)
const [previewContentHTML, setPreviewContentHTML] = useState('') const [previewContentHTML, setPreviewContentHTML] = useState('')
@ -46,11 +49,19 @@ const TemplatePreview = ({
const reloadPreviewPDF = useCallback( const reloadPreviewPDF = useCallback(
(content, testObject = {}) => { (content, testObject = {}) => {
const templateId = documentTemplate?._id
if (!templateId) {
return
}
const requestId = ++previewRequestIdRef.current
setReloadLoading(true) setReloadLoading(true)
fetchTemplatePDF(documentTemplate._id, content, testObject, (result) => { fetchTemplatePDF(templateId, content, testObject, (result) => {
if (requestId !== previewRequestIdRef.current) {
return
}
setReloadLoading(false) setReloadLoading(false)
if (result?.error) { if (result?.error) {
// Handle error through parent component
onPreviewMessage(result.error, true) onPreviewMessage(result.error, true)
} else { } else {
const pdfBlob = new Blob([result.pdf], { type: 'application/pdf' }) const pdfBlob = new Blob([result.pdf], { type: 'application/pdf' })
@ -66,16 +77,24 @@ const TemplatePreview = ({
const reloadPreview = useCallback( const reloadPreview = useCallback(
(content, testObject = {}, scale = 1) => { (content, testObject = {}, scale = 1) => {
const templateId = documentTemplate?._id
if (!templateId) {
return
}
const requestId = ++previewRequestIdRef.current
setReloadLoading(true) setReloadLoading(true)
fetchTemplatePreview( fetchTemplatePreview(
documentTemplate._id, templateId,
content, content,
testObject, testObject,
scale, scale,
(result) => { (result) => {
if (requestId !== previewRequestIdRef.current) {
return
}
setReloadLoading(false) setReloadLoading(false)
if (result?.error) { if (result?.error) {
// Handle error through parent component
onPreviewMessage(result.error, true) onPreviewMessage(result.error, true)
} else { } else {
updatePreviewContentHTML(result.html) updatePreviewContentHTML(result.html)
@ -87,18 +106,30 @@ const TemplatePreview = ({
[fetchTemplatePreview, onPreviewMessage, documentTemplate?._id] [fetchTemplatePreview, onPreviewMessage, documentTemplate?._id]
) )
const templateContent = documentTemplate?.content
const templateId = documentTemplate?._id
// Move useEffect to component level and use state to track objectData changes // Move useEffect to component level and use state to track objectData changes
useEffect(() => { useEffect(() => {
if (documentTemplate?.content) { if (!templateId || !templateContent) {
if (previewType == 'HTML') { return
reloadPreview(documentTemplate.content, objectData, previewScale)
} else {
reloadPreviewPDF(documentTemplate.content, objectData)
} }
const timeoutId = window.setTimeout(() => {
if (previewType == 'HTML') {
reloadPreview(templateContent, objectData, previewScale)
} else {
reloadPreviewPDF(templateContent, objectData)
}
}, 300)
return () => {
window.clearTimeout(timeoutId)
} }
}, [ }, [
objectData, objectData,
documentTemplate, templateContent,
templateId,
previewScale, previewScale,
previewType, previewType,
reloadPreview, reloadPreview,
@ -197,8 +228,8 @@ TemplatePreview.propTypes = {
isEditing: PropTypes.bool, isEditing: PropTypes.bool,
style: PropTypes.object, style: PropTypes.object,
showTestObject: PropTypes.bool, showTestObject: PropTypes.bool,
onTestObjectOpen: PropTypes.func.isRequired, onTestObjectOpen: PropTypes.func,
onPreviewMessage: PropTypes.func.isRequired, onPreviewMessage: PropTypes.func,
showPreviewSwitch: PropTypes.bool showPreviewSwitch: PropTypes.bool
} }

View File

@ -1691,6 +1691,9 @@ const ApiServerProvider = ({ children }) => {
callback callback
) )
} }
if (typeof callback === 'function') {
callback({ error: 'Api Server disconnected' })
}
} }
const fetchTemplatePDF = async (id, content, testObject, callback) => { const fetchTemplatePDF = async (id, content, testObject, callback) => {
@ -1706,6 +1709,9 @@ const ApiServerProvider = ({ children }) => {
callback callback
) )
} }
if (typeof callback === 'function') {
callback({ error: 'Api Server disconnected' })
}
} }
const downloadTemplatePDF = async ( const downloadTemplatePDF = async (